#' Set \code{x|yAxis} of Echarts (Primary or Secondary)
#'
#' When an echart object is generated, you can modify it by setting axis using
#' \code{\link{\%>\%}}. \cr \cr
#' You can use work functions \code{setXAxis}, \code{setYAxis}, \code{setX1Axis},
#' \code{setY1Axis}. \cr \cr
#' This function modified a few default options for the axis component in
#' ECharts:
#' \enumerate{
#'  \item \code{scale = TRUE} (was \code{FALSE} by default in ECharts);
#'  \item \code{axisLine$onZero = FALSE} (was \code{TRUE} in ECharts).
#' }
#' @param chart \code{echarts} object generated by \code{\link{echart}} or \code{\link{echartR}}
#' @param series Which series to be put on this axis. Could be:
#' \itemize{
#'  \item series names, in vectors. E.g, \code{c('setosa', 'virginica')};
#'  \item index of series, in vectors. E.g., \code{1:2} or \code{c(1,3)}.
#' }
#' @param which Which axis to be modified. Could be one of the following:
#' \describe{
#'  \item{x}{primary x axis}
#'  \item{y}{primary y axis}
#'  \item{x1}{secondary x axis}
#'  \item{y1}{secondary y axis}
#' }
#' @param type Type of the axis. Could be \code{c('time', 'value', 'category', 'log')}.
#' Default 'value'.
#' @param show Logical, whether to show this axis. Default TRUE.
#' @param position Position of this axis. Could be \code{c('bottom', 'top', 'left',
#' 'right')} (default for primary x axis, secondary x axis, primary y axis and secondary
#' y axis, respectively.)
#' @param name Name of this axis. Default not set ("").
#' @param nameLocation Axis name location. Could be \code{c('end', 'start')}. Default
#' 'end'.
#' @param nameTextStyle Axis name text style. Could be a list of \code{textStyle}
#' features. Default following textStyle global settings.
#' @param boundaryGap A two-element numeric vector, defining the policy of the space
#' at the two ends of the axis (percents). Deafult \code{c(0, 0)}.
#' @param min The mininum value of the axis. Default NULL (automatic). If a numeric
#' value is set, \code{boundaryGap} is disabled.
#' @param max The maxinum value of the axis. Default NULL (automatic). If a numeric
#' value is set, \code{boundaryGap} is disabled.
#' @param scale Logical, for axis of 'value', 'time', 'log' type, to define whether
#' zoom the scale to the range between _min and _max. Default TRUE.
#' @param splitNumber Numeric, how many sections to devide the axis. Default NULL,
#' automatically deviding based on algorithms of \code{min} and \code{max}.
#' @param axisLine A list. Default: \cr
#' \code{list(show=TRUE, onZero=FALSE, lineStyle=list( \cr
#' type='solid', color='#48b', width=2, shadowColor='rgba(0,0,0,0)', shadowBlur=5,
#' shadowOffsetX=3, shadowOffsetY=3))} \cr \cr
#' \code{lineStyle} accepts features \code{color, width, type, shadowColor, shadowBlur,
#' shadowOffsetX, shadowOffsetY}
#' @param axisTick A list. Default: \cr
#' \code{show=FALSE, inside=FALSE, length=5, lineStyle=list(color="#333", width=1)} \cr \cr
#' \code{lineStyle} accepts feature \code{color, width, type, shadowColor, shadowBlur,
#' shadowOffsetX, shadowOffsetY}
#' @param axisLabel A list controlling the axis labels. Default \code{show=TRUE,
#' rotate=0, margin=8, clickable=FALSE, formatter=NULL, textStyle=list(color="#333")} \cr \cr
#' \code{textStyle} accepts features \code{color, align, baseline, fontFamily, fontSize,
#' fontStyle, fontWeight}. \cr \cr
#' \strong{\code{formatter}}:
#' \describe{
#'  \item{sprintf/format string}{String to overide \code{axisLable$formatter}.
#'  It accepts \code{sprintf} (category and value) and \code{strptime} (time) formats.}
#'  \item{js mode}{a JS function/expression, which is default}
#' } \cr
#' \code{axisLabel=list(formatter="\%s cm")} is equal to \cr
#' \code{axisLabel=list(formatter=JS('function (value) {return value + "cm";}'))} or \cr
#' \code{axisLabel=list(formatter='{value} cm')}
#' @param splitLine A list controlling the split lines. Default \code{show=TRUE,
#' lineStyle=list(color=list("#ccc"), width=1, type="solid")} \cr \cr
#' \code{lineStyle} accepts features \code{color, width, type, shadowColor, shadowBlur,
#' shadowOffsetX, shadowOffsetY}.
#' @param splitArea A list controlling the split areas. Default \code{show=FALSE,
#' onGap=NULL, areaStyle= list(color= list("rgba(250,250,250,0.3)", "rgba(200,200,200,0.3)")
#' , type="default"} \cr \cr
#' \code{areaStyle} accepts features \code{color, type}.
#' @param data A character vector/list for axis of type 'category', to define the text
#' labels shown in this axis. Default NULL. You can even pass in a complicated list with
#' \code{textSytle} list: \cr
#' \code{list('Jan', 'Feb', 'Mar', \cr list(value='Apr', textStyle=list(color='red', ...)),
#' \cr 'May', ...)}
#'
#' @export
#' @seealso \code{\link{setXAxis}}, \code{\link{setYAxis}}, \code{\link{setX1Axis}},
#' \code{\link{setY1Axis}}
#' @references
#' \url{http://echarts.baidu.com/echarts2/doc/option.html#tooltip-line1~xAxis-i}
#'
#' \url{http://echarts.baidu.com/echarts2/doc/option.html#tooltip-line1~yAxis-i}
#' @examples
#' \dontrun{
#' g <- echartR(iris, Sepal.Width, Petal.Width, series=Species)
#'
#' # Change the style
#' g %>% setTheme('gray') %>% setXAxis(splitLine=list(show=FALSE)) %>%
#'       setYAxis(axisLine=list(lineStyle=list(width=0)))
#'
#' # Dual-yAxis, series 1,2 on primary y-axis, series 3 on secondary y-axis
#' g %>% setYAxis(1:2, name="setosa/versicolor") %>%
#'       setY1Axis("virginica", name="virginica")
#' }
setAxis = function(
    chart, series = NULL, which = c('x', 'y', 'x1', 'y1'),
    type = c('value', 'category', 'time', 'log'), show = TRUE,
    position = c('bottom', 'top', 'left', 'right'),
    name = '', nameLocation = c('end', 'start'), nameTextStyle = emptyList(),
    boundaryGap = c(0, 0), min = NULL, max = NULL, scale = TRUE, splitNumber = NULL,
    axisLine = list(show = TRUE, onZero = FALSE), axisTick = list(show = FALSE),
    axisLabel = list(show = TRUE), splitLine = list(show = TRUE),
    splitArea = list(show = FALSE), data = list()
) {
    if (!inherits(chart, 'echarts'))
        stop('chart is not an Echarts object. ',
             'Check if you have missed a %>% in your pipe chain.')
    which = match.arg(which)
    axIdx = ifna(as.integer(substr(which, 2, 3)), 0)  # 0: main ax, 1: sub ax
    which = substr(which, 0, 1)  # x or y axis
    hasT = "timeline" %in% names(chart$x)
    allSeries = getSeriesPart(chart, 'category', drop=FALSE, fetch.all=TRUE)  # all levels of series
    if (!hasT) allSeries = as.matrix(allSeries, ncol=1)
    uniSeries = unique(as.vector(allSeries))
    if (!is.null(allSeries) && !is.null(series)){
        if (is.numeric(series)){
            series = intersect(series, seq_len(nrow(allSeries)))
            series = if (length(series) > 0) allSeries[series,1] else NULL
        }else{
            series = intersect(as.character(series), unique(as.vector(allSeries)))
            if (length(series) == 0) series = NULL
        }
    }else{
        series = NULL
    }
    # get intersect of the series

    # if data partially on main axis, the rest on sub axis
    series1 = NULL
    if (length(series) > 0 && length(series) < length(uniSeries) && axIdx == 1)
        series1 = uniSeries[! uniSeries %in% series]

    # original data along the axis
    if (hasT){
        odata = lapply(chart$x$options, getMeta)
        if (!is.null(series)) {
            sdata = lapply(odata, function(lst) lst['series'])
            sdata = do.call('rbind', sdata)[,1]
        }
        odata = lapply(odata, function(lst) lst[[which]])
        odata = do.call('rbind', odata)[,1]
    }else{
        odata = getMeta(chart)[[which]][,1]
        if (!is.null(series)) sdata = getMeta(chart)[['series']][,1]
    }
    if (!is.null(series)) {
        odata1 = odata[! sdata %in% series]
        odata = odata[sdata %in% series]
    }
    # only get odata of specific series

    if (missing(type)) type = axisType(odata, which)
    if (missing(position)) {
        if (which == 'x') {
            position = if (axIdx == 0) 'bottom' else 'top'
        }else{
            position = if (axIdx == 0) 'left' else 'right'
        }
    }
    if (position %in% c('bottom', 'top'))
        position1 = c('bottom', 'top')[c('bottom', 'top') != position]
    if (position %in% c('left', 'right'))
        position1 = c('left', 'right')[c('left', 'right') != position]
    data1 = list()
    if (missing(data) && type == 'category') {
        data = unique(odata)
        #data = I(levels(as.factor(odata)))
        if (!is.null(series1))
            #data1 = I(levels(as.factor(odata1)))
            data1 = unique(odata1)
    }

    if (!is.null(axisLabel$formatter))
        if (!is.list(axisLabel$formatter)){
            axisLabel = mergeList(axisLabel, list(
                formatter=if (inherits(axisLabel$formatter, 'JS_EVAL'))
                    axisLabel$formatter else convFormat2JS(axisLabel$formatter, type))
                )
        }

    if (hasT) x = chart$x$options[[1]] else x = chart$x
    i = paste0(which, 'Axis')
    o = list(
        type = match.arg(type), show = show, position = match.arg(position),
        name = name, nameLocation = match.arg(nameLocation), nameTextStyle = nameTextStyle,
        boundaryGap = boundaryGap, min = min, max = max, scale = scale,
        splitNumber = splitNumber, axisLine = axisLine, axisTick = axisTick,
        axisLabel = axisLabel, splitLine = splitLine, splitArea = splitArea, data = data
    )
    if (!is.null(series1))
        o1 = list(
            type = match.arg(type), show = show, position = position1, #name = name,
            nameLocation = match.arg(nameLocation),
            nameTextStyle = nameTextStyle, boundaryGap = boundaryGap, min = min,
            max = max, scale = scale, splitNumber = splitNumber, axisLine = axisLine,
            axisTick = axisTick, axisLabel = axisLabel, splitLine = splitLine,
            splitArea = splitArea, data = data1
        )
    if (length(x[[i]]) > 0) {
        # only merge the arguments that are not missing, e.g. eAxis(min = 0) will
        # only overide 'min' but will not overide the 'name' attribute
        a = intersect(names(as.list(match.call()[-1])), names(o))
        if (!is.null(series1))
            a1 = intersect(names(as.list(match.call()[-1])), names(o1))
        if (length(x[[i]]) < 2 && axIdx == 1)
            x[[i]][[axIdx+1]] <- mergeList(list(), o)
        x[[i]][[axIdx+1]] = mergeList(x[[i]][[axIdx+1]], o[a])

        if (!is.null(series1))
            x[[i]][[2-axIdx]] = mergeList(x[[i]][[2-axIdx]], o1[a1])
    } else {
        x[[i]][[axIdx+1]] = list()
        x[[i]][[axIdx+1]] = mergeList(x[[i]][[axIdx+1]], o)
        if (!is.null(series1)){
            if (length(x[[i]][[2-axIdx]]) == 0) x[[i]][[2-axIdx]] = list()
            x[[i]][[2-axIdx]] = mergeList(x[[i]][[2-axIdx]], o1)
        }
    }
    if (hasT) chart$x$options[[1]] = x else chart$x = x

    # revise axisIndex
    ax = paste0(which, "AxisIndex")
    if (!is.null(series1)){
        if (axIdx == 0) seriesAx1 = series1 else seriesAx1 = series
        if (hasT){
            for (i in 1:length(chart$x$options)){
                seriesAx1 = which(allSeries[,i] %in% seriesAx1)
                for (j in seriesAx1) chart$x$options[[i]]$series[[j]][[ax]] = 1
            }
        }else{
            seriesAx1 = which(allSeries[,1] %in% seriesAx1)
            for (j in seriesAx1) chart$x$series[[j]][[ax]] = 1
        }
    }

    return(chart)
}

#' @export
#' @rdname setAxis
set_axis <- setAxis

#' @export
#' @rdname setAxis
setYAxis = function(chart, ...) {  # set primary y axis
    setAxis(chart, which = 'y', ...)
}

#' @export
#' @rdname setAxis
set_axis_y <- setYAxis

#' @export
#' @rdname setAxis
setY1Axis = function(chart, ...) {  # set secondary y axis
    setAxis(chart, which = 'y1', ...)
}

#' @export
#' @rdname setAxis
set_axis_y1 <- setY1Axis

#' @export
#' @rdname setAxis
setXAxis = function(chart, ...) {  # set primary x axis
    setAxis(chart, which = 'x', ...)
}

#' @export
#' @rdname setAxis
set_axis_x <- setXAxis


#' @export
#' @rdname setAxis
setX1Axis = function(chart, ...) {  # set secondary x axis
    setAxis(chart, which = 'x1', ...)
}

#' @export
#' @rdname setAxis
set_axis_x1 <- setX1Axis

axisType = function(data, which = c('x', 'y')) {
    if (is.numeric(data) || is.null(data)) return('value')
    if (is.factor(data) || is.character(data)) return('category')
    if (inherits(data, c('Date', 'POSIXct', 'POSIXlt'))) return('time')
    message('The structure of the ', which, ' variable:')
    str(data)
    stop('Unable to derive the axis type automatically from the ', which, ' variable')
}

flipAxis <- function(chart, flip=TRUE, ...){
    # flip x|y-axis
    if (!flip) return(chart)
    if (!inherits(chart, 'echarts'))
        stop('chart is not an Echarts object. ',
             'Check if you have missed a %>% in your pipe chain.')
    hasT <- 'timeline' %in% names(chart$x)
    if (hasT){
        if ('xAxis' %in% names(chart$x$options[[1]]) &&
            'yAxis' %in% names(chart$x$options[[1]])) {
            axes <- exchange(chart$x$options[[1]]$xAxis,
                             chart$x$options[[1]]$yAxis)
            chart$x$options[[1]]$xAxis <- axes[[1]]
            chart$x$options[[1]]$yAxis <- axes[[2]]
        }
    }else{
        if ('xAxis' %in% names(chart$x) &&
            'yAxis' %in% names(chart$x)){
            axes <- exchange(chart$x$xAxis, chart$x$yAxis)
            chart$x$xAxis <- axes[[1]]
            chart$x$yAxis <- axes[[2]]
        }
    }
    return(chart)
}


#' Set \code{grid} of Echarts Widgets And Pane
#'
#' When an echart object is generated, you can modify it by setting grid using
#' \code{\link{\%>\%}}. \cr
#' \strong{It is recommended to put \code{setGrid} at the end of the piped command.} \cr
#' When used for 'pane', it is only applicable for \code{scatter, point, bubble,
#' line, area, bar, histogram}. When used for 'timeline', it only take in params
#' \code{x, y, x2, y2}. When used for 'legend', 'title', 'dataZoom', 'dataRange',
#'  'toolbox', 'roamController', it only takes in params \code{x, y}.
#'
#' @param chart \code{echarts} object generated by \code{\link{echart}} or \code{\link{echartR}}
#' @param widget Widget name to set. Could be \code{c('pane', 'timeline', 'legend',
#' 'title', 'dataZoom', 'dataRange', 'toolbox', 'roamController')}.
#' \describe{
#' \item{pane}{the area pane, takes in all the parameters}
#' \item{timeline}{timeline widget, only use \code{x, y, x2, y2}}
#' \item{legend, title, dataZoom, dataRange, toolbox, roamController}{other widgets,
#' only use \code{x, y}}
#' }
#' @param x x coordinate of the left upper point of the plot area. Default 80px.
#' @param y y coordinate of the left upper point of the plot area. Default 60px.
#' @param x2 x coordinate of the right lower point of the plot area. Default 80px.
#' @param y2 y coordinate of the right lower point of the plot area. Default 60px.
#' @param width Width of the plot area. Default NULL (automatically configured)
#' @param height Height of the plot area. Default NULL (automatically configured)
#' @param bgColor background color of plot area. Default transparent ('rgba(0,0,0,0)').
#' @param borderColor border color of the plot area. Default '#ccc'.
#' @param borderWidth border width of the plot area. Default 0px (not shown).
#' @param ... Elipsis.
#'
#' @return A modified echarts object
#' @export
#' @seealso \code{\link{relocWidget}}
#'
#' @references \url{http://echarts.baidu.com/echarts2/doc/option.html#title~grid}
#' @examples
#' \dontrun{
#' g <- iris %>% echartR(x=Sepal.Width, y=Petal.Width, series=Species)
#' g %>% setGrid(x=40, y=40, x2=70, y2=30, bgColor='gray90')
#' }
setGrid <- function(chart, x=80, y=60, x2=80, y2=60, width=NULL, height=NULL,
                    bgColor=NULL, borderColor=NULL, borderWidth=1,
                    widget=c('pane', 'timeline', 'legend', 'title', 'dataZoom',
                             'dataRange', 'toolbox', 'roamController'), ...){
    if (!inherits(chart, 'echarts'))
        stop('chart is not an Echarts object. ',
             'Check if you have missed a %>% in your pipe chain.')
    hasT <- 'timeline' %in% names(chart$x)
    types <- getSeriesPart(chart, 'type')
    widget <- match.arg(widget)
    if (widget == 'pane') widget <- 'grid'
    lstGrid <- list()
    if (! missing(x)) lstGrid[['x']] <- unname(x)
    if (! missing(y)) lstGrid[['y']] <- unname(y)
    if (widget %in% c('grid', 'timeline')){
        if (! missing(x2)) lstGrid[['x2']] <- unname(x2)
        if (! missing(y2)) lstGrid[['y2']] <- unname(y2)
    }
    if (widget == 'grid'){
        if (! missing(width)) lstGrid[['width']] <- width
        if (! missing(height)) lstGrid[['height']] <- height
        if (! missing(borderWidth)) lstGrid[['borderWidth']] <- borderWidth
        if (! missing(borderColor)) lstGrid[['borderColor']] <- borderColor
        if (! missing(bgColor)) lstGrid[['backgroundColor']] <- getColors(bgColor)[1]
    }
    ## wrap up
    if (hasT){
        if (widget == 'timeline'){
            chart$x$timeline <- mergeList(chart$x$timeline, lstGrid)
        }else if((widget == 'grid') ||
                  widget %in% names(chart$x$options[[1]])){
            chart$x$options[[1]][[widget]] <-
                if (is.null(chart$x$options[[1]][[widget]])) lstGrid else
                    mergeList(chart$x$options[[1]][[widget]], lstGrid)
        }
    }else{
        if ((widget == 'grid') ||
            widget %in% names(chart$x))
                chart$x[[widget]] <- if (is.null(chart$x[[widget]]))
                    lstGrid else mergeList(chart$x[[widget]], lstGrid)
    }

    return(chart)
}

#' @export
#' @rdname setGrid
set_grid <- setGrid

#' @export
#' @rdname setGrid
relocTitle <- function(chart, x=NULL, y=NULL, ...){
    setGrid(chart, x=x, y=y, widget='title', ...)
}

#' @export
#' @rdname setGrid
move_title <- relocTitle

#' @export
#' @rdname setGrid
relocLegend <- function(chart, x=NULL, y=NULL, ...){
    setGrid(chart, x=x, y=y, widget='legend', ...)
}

#' @export
#' @rdname setGrid
move_legend <- relocLegend

#' @export
#' @rdname setGrid
relocDataZoom <- function(chart, x=NULL, y=NULL, ...){
    setGrid(chart, x=x, y=y, widget='dataZoom', ...)
}

#' @export
#' @rdname setGrid
move_datazoom <- relocDataZoom

#' @export
#' @rdname setGrid
move_dz <- relocDataZoom

#' @export
#' @rdname setGrid
relocDataRange <- function(chart, x=NULL, y=NULL, ...){
    setGrid(chart, x=x, y=y, widget='dataRange', ...)
}

#' @export
#' @rdname setGrid
move_datarange <- relocDataRange

#' @export
#' @rdname setGrid
move_dr <- relocDataRange

#' @export
#' @rdname setGrid
relocTimeline <- function(chart, x=NULL, y=NULL, x2=NULL, y2=NULL, ...){
    setGrid(chart, x=x, y=y, x2=x2, y2=y2, widget='timeline', ...)
}

#' @export
#' @rdname setGrid
move_timeline <- relocTimeline

#' @export
#' @rdname setGrid
move_tl <- relocTimeline

#' @export
#' @rdname setGrid
relocToolbox <- function(chart, x=NULL, y=NULL, ...){
    setGrid(chart, x=x, y=y, widget='toolbox', ...)
}

#' @export
#' @rdname setGrid
move_toolbox <- relocToolbox

#' @export
#' @rdname setGrid
move_tb <- relocToolbox

#' @export
#' @rdname setGrid
relocRoam <- function(chart, x=NULL, y=NULL, ...){
    setGrid(chart, x=x, y=y, widget='roamController', ...)
}

#' @export
#' @rdname setGrid
move_roam <- relocRoam

#' @export
#' @rdname setGrid
move_rc <- relocRoam

#' Re-locate Echarts Widgets (Position of Upper-left/Lower-right Point)
#'
#' @param chart Echarts object
#' @param widgets Vector or list, could be \code{'title', 'timeline', 'legend', 'toolbox',
#' 'dataRange', 'dataZoom', 'roamController'}
#' @param x Vector, x-coordinates of the widgets' upper-left point
#' @param y Vector, y-coordinates of the widgets' upper-left point
#' @param x2 Vector, x-coordinates of the widgets' lower-right point
#' @param y2 Vector, y-coordinates of the widgets' lower-right point
#' @note If \code{x, y, x2, y2} are shorter in length than the list \code{widgets},
#' the last element of \code{x, y, x2, y2} will be applied to cover the rest.
#' If \code{x, y, x2, y2} are longer in length than the list \code{widgets},
#' the redundent elements will be dropped.
#' @return A modified Echarts object
#' @export
#'
#' @seealso \code{\link{setGrid}}
#' @examples
#' \dontrun{
#' g <- echartR(iris, Sepal.Width, Petal.Width) %>% setDataZoom()
#' g %>% relocWidgets('dataZoom', x=150)
#' }
relocWidget <- function(chart, widgets, x=NULL, y=NULL, x2=NULL, y2=NULL){
    if (!inherits(chart, 'echarts'))
        stop('chart is not an Echarts object. ',
             'Check if you have missed a %>% in your pipe chain.')
    stopifnot(all(widgets %in% c('title', 'timeline', 'legend', 'toolbox',
                                 'dataRange', 'dataZoom', 'roamController')))
    if (!missing(x)) if (!is.null(x)) x <- if (length(x) < length(widgets))
        c(x, rep(x[length(x)], length(widgets) - length(x))) else x[length(widgets)]
    if (!missing(y)) if (!is.null(y)) y <- if (length(y) < length(widgets))
        c(y, rep(y[length(y)], length(widgets) - length(y))) else x[length(widgets)]
    if (!missing(x2)) if (!is.null(x2)) x <- if (length(x2) < length(widgets))
        c(x2, rep(x2[length(x2)], length(widgets) - length(x2))) else x[length(widgets)]
    if (!missing(y2)) if (!is.null(y2)) x <- if (length(y2) < length(widgets))
        c(y2, rep(y2[length(y2)], length(widgets) - length(y2))) else x[length(widgets)]
    for (i in 1:length(widgets)){
        chart <- chart %>% setGrid(x[i], y[i], x2[i], y2[i], widget=widgets[i])
    }
    return(chart)
}

#' @rdname relocWidget
#' @export
move_widget <- relocWidget

.getGridParam <- function(chart, control, pos, size, horizontal=TRUE){
    stopifnot(length(pos) == 4)  ## x, y, x2, y2
    stopifnot(length(size) == 2) ## height, width
    hasT <- 'timeline' %in% names(chart$x)
    if (hasT){
        if (control == 'timeline')  obj <- chart$x$timeline
        else obj <- chart$x$options[[1]][[control]]
    }else{
        obj <- chart$x[[control]]
    }
    lst <- lapply(c('x', 'y', 'x2', 'y2', 'orient', 'height', 'width'),
                  function(param){obj[[param]]})
    lstDefault <- c(as.list(pos), ifelse(horizontal, 'horizontal', 'vertical'),
                    as.list(size))
    names(lst) <- names(lstDefault) <-  c('x', 'y', 'x2', 'y2', 'orient',
                                          'height', 'width')

    if (!is.null(obj))
        lst <- unlist(mergeList(lstDefault, lst, keep.null=TRUE,
                                skip.merge.null=TRUE))
    # x, y , orient
    else return(rep(NA, 8))

    # x, y, x2, y2, width, height
    x <- ifelse(lst['x'] %in% c('left', 'center', 'right'), lst['x'],
                ifelse(grepl("document\\.getElementById", lst['x']), 'right',
                       suppressWarnings(as.numeric(lst['x']))))
    if (is.numeric(x)) x <- if (ifna(x, 0) < 80) 'left' else
        if (ifna(x, 0) < 400) 'center' else 'right'
    y <- ifelse(lst['y'] %in% c('top', 'center', 'bottom'), lst['y'],
                ifelse(grepl("document\\.getElementById", lst['y']), 'bottom',
                       suppressWarnings(as.numeric(lst['y']))))
    if (is.numeric(y)) y <- if (ifna(y, 0) < 60) 'top' else
        if (ifna(y, 0) < 400) 'center' else 'bottom'

    x2 <- suppressWarnings(as.numeric(lst['x2']))
    y2 <- suppressWarnings(as.numeric(lst['y2']))
    height <- suppressWarnings(as.numeric(lst['height']))
    width <- suppressWarnings(as.numeric(lst['width']))
    pos <- ifelse(length(clockPos(x, y, lst['orient'])) == 0, 12,
                  clockPos(x, y, lst['orient']))

    x <- suppressWarnings(as.numeric(lst['x']))
    y <- suppressWarnings(as.numeric(lst['y']))
    if (is.na(x)) x <- ifnull(switch(lst['x'], left=0, center=0, right=NA), 0)
    if (is.na(x2)) x2 <- ifnull(switch(lst['x'], left=NA, center=0, right=0), 0)
    if (is.na(y)) y <- ifnull(switch(lst['y'], top=0, center=0, bottom=NA), 0)
    if (is.na(y2)) y2 <- ifnull(switch(lst['y'], top=NA, center=0, bottom=0), 0)

    return(c(pos, x, y, x2, y2, height, width, unname(lst['orient']=='horizontal')))
}

#' Tune The Position of An Echarts Object
#'
#' The funcion is used to tune x, y, x2, x2 of an Echarts object regarding its controls,
#' such as title, timeline, legend, toolbox, dataRange, dataZoom, roamController.
#' @param chart An Echarts object
#' @export
#' @importFrom data.table data.table dcast
tuneGrid <- function(chart, ...){
    # tune the grid of pane and widgets
    if (!inherits(chart, 'echarts'))
        stop('chart is not an Echarts object. ',
             'Check if you have missed a %>% in your pipe chain.')
    types <- getSeriesPart(chart, 'type')
    hasT <- 'timeline' %in% names(chart$x)
    # if not Cartesian Coord chart, skip out

    controls <- c('title', 'timeline', 'legend', 'toolbox', 'dataRange',
                  'dataZoom', 'roamController')
    gridParam <- c('pos', 'x', 'y', 'x2', 'y2', 'height', 'width', 'orient')
    dfGrid <- data.frame(matrix(ncol=length(gridParam), nrow=length(controls)))
    colnames(dfGrid) <- gridParam
    rownames(dfGrid) <- controls

    #---------- get x, y, x1, y1 of each control --------------
    dfGrid['title',] <- .getGridParam(
        chart, 'title', c('center', 'bottom', NA, NA), c(50, 50))
    dfGrid['legend',] <- .getGridParam(
        chart, 'legend', c('left', 'top', NA, NA), c(50, 50))
    dfGrid['dataRange',] <- .getGridParam(
        chart, 'dataRange', c('left', 'bottom', NA, NA), c(50, 120), FALSE)
    dfGrid['dataZoom',] <- .getGridParam(
        chart, 'dataZoom', c('center', 'bottom', NA, NA), c(30, 30))
    dfGrid['toolbox',] <- .getGridParam(
        chart, 'toolbox', c('right', 'top', NA, NA), c(50, 50))
    dfGrid['timeline',] <- .getGridParam(
        chart, 'timeline', c('center', 'bottom', 80, 0), c(50, 50))
    dfGrid['roamController',] <- .getGridParam(
        chart, 'roamController', c('right', 'top', NA, NA), c(80, 150), FALSE)
    # remove all NA rows
    dfGrid <- dfGrid[!(apply(dfGrid, 1, function(row) all(is.na(row)))),]

    #dfGrid <<- dfGrid
    #browser()

    sumGrid <- dcast(data.table(dfGrid), orient + pos ~ ., fun=sum,
                     value.var=c("x", "y", "x2", "y2", "height", "width"))
    names(sumGrid) <- c('orient', 'pos', 'x', 'y', 'x2', 'y2', 'height', 'width')
    uniqueGrid <- dfGrid[!duplicated(paste(dfGrid$orient, dfGrid$pos)),]
    uniqueGrid <- uniqueGrid[order(uniqueGrid$orient, uniqueGrid$pos),]
    # sumGrid[,c('x_sum_.', 'y_sum_.', 'x2_sum_.', 'y2_sum_.')] <-
    #     uniqueGrid[, c('x', 'y', 'x2', 'y2')]
    # sumGrid$x <- ifblank(
    #     rowSums(sumGrid[,list(x_sum_., width_sum_.)], na.rm=TRUE), NA)
    # sumGrid$y <- ifblank(
    #     rowSums(sumGrid[,list(y_sum_., height_sum_.)], na.rm=TRUE), NA)
    # sumGrid$x2 <- ifblank(
    #     rowSums(sumGrid[, list(x2_sum_., width_sum_.)], na.rm=TRUE), NA)
    # sumGrid$y2 <- ifblank(
    #     rowSums(sumGrid[, list(y2_sum_., height_sum_.)], na.rm=TRUE), NA)
    sumGrid[,c('x', 'y', 'x2', 'y2')] <- uniqueGrid[, c('x', 'y', 'x2', 'y2')]
    sumGrid$x <- ifblank(
        rowSums(sumGrid[,list(x, width)], na.rm=TRUE), NA)
    sumGrid$y <- ifblank(
        rowSums(sumGrid[,list(y, height)], na.rm=TRUE), NA)
    sumGrid$x2 <- ifblank(
        rowSums(sumGrid[, list(x2, width)], na.rm=TRUE), NA)
    sumGrid$y2 <- ifblank(
        rowSums(sumGrid[, list(y2, height)], na.rm=TRUE), NA)

    #uniqueGrid <<- uniqueGrid
    #sumGrid <<- sumGrid

    lstGrid <- list()
    if (length(sumGrid[pos %in% c(8, 9, 10), x]) > 0)
        if (max(sumGrid[pos %in% c(8, 9, 10), x]) > 70)
            lstGrid$x <- unname(max(ifblank(sumGrid[pos == 9, x], 70)) + 20)
    if (length(sumGrid[pos %in% c(11, 12, 1), y]) > 0)
        if (max(sumGrid[pos %in% c(11, 12, 1), y]) > 50)
            lstGrid$y <- unname(max(ifblank(sumGrid[pos == 12, y], 50)) + 30)
    if (length(sumGrid[pos %in% c(2, 3, 4), x2]) > 0)
        if (max(sumGrid[pos %in% c(2, 3, 4), x2]) > 70)
            lstGrid$x2 <- unname(max(ifblank(sumGrid[pos == 3, x2], 70)) + 20)
    if (length(sumGrid[pos %in% c(5, 6, 7), y2]) > 0)
        if (max(sumGrid[pos %in% c(5, 6, 7), y2]) > 50)
            lstGrid$y2 <- unname(max(ifblank(sumGrid[pos == 6, y2], 50)) + 30)

    ## tune grid if there are duplicated pos
    if (any(duplicated(dfGrid$pos))){
        dupPos <- table(dfGrid$pos)
        dupPos <- as.numeric(names(dupPos[dupPos > 1]))
        for (i in dupPos){
            dfDupGrid <- dfGrid[dfGrid$pos == i,]
            len <- nrow(dfDupGrid)
            widgets <- row.names(dfDupGrid)[2:len]
            widgetsNotTL <- widgets[!widgets %in% 'timeline']
            dfDupGrid$cumHeight <- cumsum(dfDupGrid$height)
            dfDupGrid$cumWidth <- cumsum(dfDupGrid$width)
            sizeParam = ifelse(i %in% c(1, 5, 6, 7, 11, 12), 'height', 'width')

            if (i %in% c(11, 12, 1)){
                cumSize <- ifna(dfDupGrid[1, 'y'],0) +
                    dfDupGrid[1: (len - 1), c("cumHeight")]
                w = 'y'
                w2 = 'y2'
            }else if (i %in% c(2, 3, 4)){
                cumSize <- ifna(dfDupGrid[1, 'x2'],0) +
                    dfDupGrid[1: (len - 1), c("cumWidth")]
                w = 'x2'
                w2 = 'x'
            }else if (i %in% c(5, 6, 7)){
                cumSize <- ifna(dfDupGrid[1, 'y2'],0) +
                    dfDupGrid[1: (len - 1), c("cumHeight")]
                w = 'y2'
                w2 = 'y'
            }else if (i %in% c(8, 9, 10)){
                cumSize <- ifna(dfDupGrid[1, 'x'],0) +
                    dfDupGrid[1: (len - 1), c("cumWidth")]
                w = 'x'
                w2 = 'x2'
            }

            names(cumSize) <- widgets

            if (hasT){
                for (j in widgets){
                    if (j == 'timeline') chart$x[[j]][[w]] = unname(cumSize[j])
                    else {
                        chart$x$options[[1]][[j]][[w]] = unname(cumSize[j])
                        if (w %in% c('x2', 'y2'))
                             chart$x$options[[1]][[j]][[w2]] =
                                 dev.size('px')[ifelse(w == 'x2', 1, 2)] - 10 -
                                 dfGrid[j, sizeParam] - chart$x$options[[1]][[j]][[w]]
                            # chart$x$options[[1]][[j]][[w2]] = JS(
                            #     paste0(getJSElementSize(
                            #         chart, ifelse(w == 'x2', 'width', "height")),
                            #         " - ", dfGrid[j, sizeParam] +
                            #             chart$x$options[[1]][[j]][[w]]))
                    }
                }
            }else{
                for (j in widgets) {
                    chart$x[[j]][[w]] = unname(cumSize[j])
                    if (w %in% c('x2', 'y2'))
                        chart$x[[j]][[w2]] =
                            dev.size('px')[ifelse(w == 'x2', 1, 2)] - 10 -
                            dfGrid[j, sizeParam] - chart$x[[j]][[w]]
                        # chart$x[[j]][[w2]] = JS(
                        #     paste0(getJSElementSize(
                        #         chart, ifelse(w == 'x2', 'width', "height")),
                        #         " - ", dfGrid[j, sizeParam] +
                        #             chart$x[[j]][[w]]))
                }
            }
        }
    }

    ## additional tuning
    if ('dataZoom' %in% row.names(dfGrid))
        if (dfGrid['dataZoom', 'orient'] == 1){
            if (hasT) chart$x$options[[1]]$dataZoom$x <- ifnull(lstGrid$x, 80)
            else chart$x$dataZoom$x <- ifnull(lstGrid$x, 80)
        }else{
            if (hasT) chart$x$options[[1]]$dataZoom$y <- ifnull(lstGrid$y, 60)
            else chart$x$dataZoom$y <- ifnull(lstGrid$y, 60)
        }
    if ('timeline' %in% row.names(dfGrid)){
        chart$x$timeline$x <- ifnull(lstGrid$x, 80)
        chart$x$timeline$x2 <- ifnull(lstGrid$x2, 80)
    }

    ## wrap up
    # collect all grid features
    if (all(types %in% c('scatter', 'line', 'bar', 'k', 'eventRiver')))
        if (length(lstGrid) > 0){
            if (hasT)
                chart$x$options[[1]][['grid']] <- lstGrid
            else
                chart$x[['grid']] <- lstGrid
        }
    return(chart)
}

#' @rdname tuneGrid
#' @export
tune_grid <- tuneGrid

makeTitle <- function(title=NULL, subtitle=NULL, link=NULL, sublink=NULL,
                      pos=6, bgColor=NULL, borderColor=NULL,
                      borderWidth=NULL, textStyle=NULL, subtextStyle=NULL, ...){
    # Work function for setTitle
    lstTitle <- list()

    if (!is.null(title)){
        if (grepl("^\\[.+\\]\\(.+\\)$", title)){
            if (is.null(link)) link <- gsub("^\\[(.+)\\]\\((.+)\\)$", "\\2", title)
            title <- gsub("^\\[(.+)\\]\\((.+)\\)$", "\\1", title)
        }
        lstTitle$text <- title
        if (!is.null(link)) lstTitle$link <- link
    }
    if (!is.null(subtitle)){
        if (grepl("^\\[.+\\]\\(.+\\)$", subtitle)){
            if (is.null(sublink))
                sublink <- gsub("^\\[(.+)\\]\\((.+)\\)$", "\\2", subtitle)
            subtitle <- gsub("^\\[(.+)\\]\\((.+)\\)$", "\\1", subtitle)
        }
        lstTitle$subtext <- subtitle
        if (!is.null(sublink)) lstTitle$sublink <- sublink
    }

    if (is.numeric(pos[[1]]) && pos[[1]] <= 12){
        lstTitle[c('x', 'y', 'orient')] <- vecPos(pos[[1]])
    }else if (length(pos)==3 && tolower(pos[[1]]) %in%
              c('left', 'right', 'center') &&
              tolower(pos[[2]]) %in% c('top', 'center', 'bottom') &&
              tolower(pos[[3]]) %in% c('vertical', 'horizontal')){
        lstTitle[c('x', 'y', 'orient')] <- pos
    }

    if (!is.null(bgColor)){
        if (bgColor != 'rgba(0,0,0,0)')
            lstTitle[['backgroundColor']] <- getColors(bgColor)[1]
    }
    if (!is.null(borderColor)){
        if (borderColor != '#ccc')
            lstTitle[['borderColor']] <- getColors(borderColor)[1]
    }
    if (!is.null(borderWidth)){
        if (borderWidth > 0)
            lstTitle[['borderWidth']] <- borderWidth
    }
    if (!is.null(textStyle)){
        if (!identical(textStyle, list(fontSize=18, fontWeight='bold', color='#333'))){
            if (all(names(textStyle) %in% c('fontSize', 'fontWeight', 'fontStyle',
                                            'color', 'fontFamily')))
                lstTitle[['textStyle']] <- textStyle
        }
    }
    if (!is.null(subtextStyle)){
        if (!identical(subtextStyle, list(color='#aaa'))){
            if (all(names(subtextStyle) %in% c('fontSize', 'fontWeight', 'fontStyle',
                                               'color', 'fontFamily')))
                lstTitle[['subtextStyle']] <- subtextStyle
        }
    }

    return(lstTitle)
}

#' Set \code{title} (And Subtitle) of Echarts
#'
#' When an echart object is generated, you can modify it by setting title and
#' subtitles using \code{\link{\%>\%}}.
#'
#' In echarts with timeline, you can set \code{title, subtitle, link, sublink, bgColor,
#' borderColor, borderWidth} as vectors of the same length as \code{t}. For \code{textSytle,
#' subtextStyle}, you need to pack the user-defined lists in a list.
#' @param chart \code{echarts} object generated by \code{\link{echart}} or \code{\link{echartR}}
#' @param title text of the title. If written in markdown format \code{[caption](url)},
#' then \code{caption} is passed to title, \code{url} is passed to link. If the length
#' of the title vector equals to the length of timeline slices, the title vector will
#' be used as slice-specific user-defined title.
#' @param link link of the title
#' @param subtitle text of the subtitle. If written in markdown format \code{[caption](url)},
#' then \code{caption} is passed to title, \code{url} is passed to link. If the length
#' of the subtitle vector equals to the length of timeline slices, the subtitle vector will
#' be used as slice-specific user-defined title.
#' @param sublink link of the subtitle
#' @param pos the clock-position of title (and subtitle), refer to \code{\link{vecPos}}. Or
#' define a vector \code{c(x, y, orient)} yourself.
#' @param bgColor background color of title. Default 'rgba(0,0,0,0)' (transparent)
#' @param borderColor border color of the title. Default '#ccc'.
#' @param borderWidth border width of the title. Default 0px (not shown).
#' @param textStyle You can place self-defined textStyle list of the title here. E.g., \code{list(
#' fontFamily='Arial|Verdana|sans-serif', fontSize=20, fontWeight='normal|bold|bolder|lighter|<numbers>',
#' fontStyle='normal|italic|oblique', color='red')}
#' @param subtextStyle You can place self-defined textStyle of the subtitle here.
#' @param show Logical. Whether to show the title. If you want to remove title from
#' the echarts object, set it NULL.
#' @param ... elipsis
#'
#' @return A modified echart object
#' @export
#'
#' @references
#' \url{http://echarts.baidu.com/echarts2/doc/option.html#title~title}
#'
#' @examples
#' \dontrun{
#' ## simple echarts scatter plot
#' g <- iris %>% echartR(x=Sepal.Width, y=Petal.Width, series=Species,
#'                       type='scatter')
#'
#' ## set simple titles
#' g %>% setTitle(title='Iris data set', subtitle='by: R. A. Fisher')
#'
#' ## set titles with links and textStyles
#' g %>% setTitle(
#'   '[Iris data set](https://en.wikipedia.org/wiki/Iris_flower_data_set)',
#'   '[R. A. Fisher](https://en.wikipedia.org/wiki/Ronald_Fisher)',
#'   textStyle=textStyle(fontFamily='Courier New', fontSize=24, color='gold'),
#'   subtextStyle=textStyle(color='silver'), bgColor='lightgreen')
#'
#'
#' ## echarts with timeline
#' g1 <- iris %>% echartR(x=Sepal.Width, y=Petal.Width, t=Species, type='scatter')
#'
#' ## simple titles/subtitles
#' g1 %>% setTitle(
#'     '[Iris data set](https://en.wikipedia.org/wiki/Iris_flower_data_set)',
#'     '[R. A. Fisher](https://en.wikipedia.org/wiki/Ronald_Fisher)')
#'
#' ## user-defined titles/subtitles
#' titles <- c(
#'     '[Iris setosa](https://en.wikipedia.org/wiki/Iris_setosa)',
#'     '[Iris versicolor](https://en.wikipedia.org/wiki/Iris_versicolor)',
#'     '[Iris virginica](https://en.wikipedia.org/wiki/Iris_virginica)')
#'
#' g1 %>%
#' setTitle(
#'    titles,
#'    textStyle=list(
#'       textStyle(fontFamily='Impact', color='red', fontStyle='normal'),
#'       textStyle(fontFamily='Times New Roman', color='green'),
#'       textStyle(fontFamily='Calibri', color='blue', fontStyle='oblique')
#'    ),
#'    bgColor='lightyellow')
#' ## textStyle is a list length 3, mapping 3 levels for timeline
#' }
setTitle <- function(chart, title=NULL, subtitle=NULL, link=NULL, sublink=NULL,
                     pos=6, bgColor=NULL, borderColor=NULL, borderWidth=NULL,
                     textStyle=NULL, subtextStyle=NULL, show=TRUE, ...){
    if (!inherits(chart, 'echarts'))
        stop('chart is not an Echarts object. ',
             'Check if you have missed a %>% in your pipe chain.')
    hasT <- 'timeline' %in% names(chart$x)
    if (!missing(title)) title <- ifnull(title, "")
    if (!missing(subtitle)) subtitle <- ifnull(subtitle, "")
    if (hasT){  # has a timeline
        if (is.null(show)) {
            for (i in 1:length(chart$x$options))
                chart$x$options[[i]]$title <- NULL
            return(chart %>% tuneGrid())
        }
        if (!is.null(title)){
            if (length(title) == length(chart$x$options)){
                titles <- unlist(title)
            } else {
                if (grepl("^\\[.+\\]\\(.+\\)$", title[1])){
                    link <- gsub("^\\[(.+)\\]\\((.+)\\)$", "\\2", title[1])
                    title <- gsub("^\\[(.+)\\]\\((.+)\\)$", "\\1", title[1])
                }
                titles <- paste0(
                    rep(gsub("^\\[(.+)\\]\\((.+)\\)$", "\\1", title),
                        length(chart$x$options)), " (",
                    attr(chart$x$timeline, 'sliceby'), ": ",
                    chart$x$timeline$data, ")")
            }
        }else{
            titles <- rep(NULL, length(chart$x$options))
        }
        .dupicateThem <- function(val){
            if (!is.null(val)){
                if ((is.list(val) && is.list(val[[1]])) || is.vector(val)){
                    if (length(val) == length(chart$x$options)){
                        return(val)
                    }else{
                        return(rep(val[1], length(chart$x$options)))
                    }
                }else{
                    if (is.list(val))
                        return(rep(list(val[1]), length(chart$x$options)))
                    else
                        return(rep(val[1], length(chart$x$options)))
                }
            }else{
                return(rep(NULL, length(chart$x$options)))
            }
        }
        subtitles <- .dupicateThem(subtitle)
        links <- .dupicateThem(link)
        sublinks <- .dupicateThem(sublink)
        bgColors <- .dupicateThem(bgColor)
        borderColors <- .dupicateThem(borderColor)
        borderWidths <- .dupicateThem(borderWidth)
        textStyles <- .dupicateThem(textStyle)
        subtextStyles <- .dupicateThem(subtextStyle)

        if (! all(is.null(c(titles, subtitles, links, sublinks, bgColor, pos, show,
                            borderColor, borderWidth, textStyle, subtextStyle)))) {
            for (i in seq_len(length(chart$x$options))){
                lstTitle <- makeTitle(
                    title=titles[i], subtitle=subtitles[i], link=links[i],
                    sublink=sublinks[i], pos=pos, bgColor=bgColors[i],
                    borderColor=borderColors[i], borderWidth=borderWidths[i],
                    textStyle=textStyles[[i]], subtextStyle=subtextStyles[[i]]
                )
                chart$x$options[[i]][['title']] <-
                    if (is.null(chart$x$options[[i]][['title']])) lstTitle else
                        mergeList(chart$x$options[[i]][['title']], lstTitle)
            }
        }
    }else{  # do not have timeline
        if (is.null(show)) {
            chart$x$title <- NULL
            return(chart %>% tuneGrid)
        }
        if (! all(is.null(c(title, subtitle, link, sublink)))){
            lstTitle <- makeTitle(
                title=title, subtitle=subtitle, link=link, sublink=sublink,
                pos=pos, borderColor=borderColor, borderWidth=borderWidth,
                bgColor=bgColor, textStyle=textStyle, subtextStyle=subtextStyle
            )
            chart$x$title <- if (is.null(chart$x$title)) lstTitle else
                mergeList(chart$x$title , lstTitle)
        }
    }
    return(chart %>% tuneGrid())
}

#' @rdname setTitle
#' @export
set_title <- setTitle

makeToolbox <- function(toolbox=c(TRUE,'cn'), type='auto',
                        show=c('mark', 'dataZoom', 'dataView', 'magicType',
                               'restore', 'saveAsImage'), pos=1,
                        bgColor='rgba(0,0,0,0)', borderColor='#ccc', borderWidth=0,
                        padding=5, itemGap=10, itemSize=16,
                        color=c("#1e90ff", "#22bb22", "#4b0082", "#d2691e"),
                        disableColor='#ddd', effectiveColor='red', showTitle=TRUE,
                        textStyle=NULL,
...){
    # Work function for setToolbox

    if (! is.null(show)) show <- tolower(show)
    if (toolbox[1]){
        lstToolbox= list(
            show = ifnull(as.logical(toolbox[1]), TRUE),
            feature = list(
                mark =list(show = ('mark' %in% show)),
                dataZoom = list(show = ('datazoom' %in% show)),
                dataView = list(show = ('dataview' %in% show),
                                readOnly = FALSE),
                magicType = list(show = FALSE),
                restore = list(show = ('restore' %in% show)),
                saveAsImage = list(show = ('saveasimage' %in% show))
            )
        )
        if (! missing(bgColor)) if (bgColor != 'rgba(0,0,0,0)')
            lstToolbox$backgroundColor <- bgColor
        if (! missing(borderColor)) if (borderColor != '#ccc')
            lstToolbox$borderColor <- borderColor
        if (! missing(borderWidth)) if (borderWidth > 0)
            lstToolbox$borderWidth <- borderWidth
        if (! missing(padding)) if (padding != 5) lstToolbox$padding <- padding
        if (! missing(itemGap)) if (itemGap != 10)
            lstToolbox$itemGap <- itemGap
        if (! missing(itemSize)) if (itemSize != 16) lstToolbox$itemSize <- itemSize
        if (! missing(color)) if (!identical(color, c("#1e90ff", "#22bb22", "#4b0082", "#d2691e")))
            lstToolbox$color <- color
        if (! missing(disableColor)) if (disableColor != '#ddd')
            lstToolbox$disableColor <- disableColor
        if (! missing(effectiveColor)) if (effectiveColor != 'red')
            lstToolbox$effectiveColor <- effectiveColor
        if (! missing(showTitle)) if (! showTitle) lstToolbox$showTitle <- showTitle
        if (! missing(textStyle)) if (is.null(textStyle))
            lstToolbox$textStyle <- textStyle

        if (tolower(toolbox[2]) != 'cn'){  # Enlish tooltips of the controls
            lstToolbox[['feature']][['mark']][['title']] = list(
                mark="Apply Auxiliary Conductor",
                markUndo="Undo Auxiliary Conductor",
                markClear="Clear Auxiliary Conductor")
            lstToolbox[['feature']][['dataZoom']][['title']] = list(
                dataZoom="Data Zoom",
                dataZoomReset="Reset Data Zoom")
            lstToolbox[['feature']][['dataView']][['title']] = "Data View"
            lstToolbox[['feature']][['dataView']][['lang']] <-
                c('Data View', 'Close', 'Refresh')
            lstToolbox[['feature']][['restore']][['title']] = "Restore"
            lstToolbox[['feature']][['saveAsImage']][['title']] = "Save As Image"
            lstToolbox[['feature']][['saveAsImage']][['lang']] <- 'Click to Save'
        }

        if (is.numeric(pos[[1]]) && pos[[1]] <= 12){
            lstToolbox[c('x', 'y', 'orient')] <- vecPos(pos[[1]])
        }else if (length(pos)==3 && tolower(pos[[1]]) %in%
                  c('left', 'right', 'center') &&
                  tolower(pos[[2]]) %in% c('top', 'center', 'bottom') &&
                  tolower(pos[[3]]) %in% c('vertical', 'horizontal')){
            lstToolbox[c('x', 'y', 'orient')] <- pos
        }

        if (type[1] %in% c('auto', 'line', 'bar', 'k')){
            lstToolbox[['feature']][['magicType']] <-
                list(show=TRUE, type= c('line', 'bar', 'tiled', 'stack'))
        }else if (type[1] %in% c('pie', 'funnel')){
            lstToolbox[['feature']][['magicType']] <-
                list(show=TRUE, type= c('pie', 'funnel'))
        }else if (type[1] %in% c('force', 'chord')){
            lstToolbox[['feature']][['magicType']] <-
                list(show=TRUE, type= c('force', 'chord'))
            lstToolbox[['feature']][['dataView']] <- list(show=FALSE)
            lstToolbox[['feature']][['dataZoom']] <- list(show=FALSE)
        }

        if (lstToolbox$feature$magicType$show){
            if (tolower(toolbox[2]) != 'cn'){
                lstToolbox[['feature']][['magicType']][['title']] <- list(
                    line   = "Switch to Line Chart",
                    bar    = "Switch to Bar Chart",
                    stack  = "Stack",
                    tiled  = "Tiled",
                    force  = "Switch to Force Chart",
                    chord  = "Switch to Chord Chart",
                    pie    = "Switch to Pie Chart",
                    funnel = "Switch to Funnel Chart"
                )
            }
        }
    }else{
        lstToolbox=list(show=FALSE)
    }
    return(lstToolbox)
}

#' Set \code{toolbox} of Echarts
#'
#' When an echart object is generated, you can modify it by setting toolbox using
#' \code{\link{\%>\%}}.
#'
#' @param chart \code{echarts} object generated by \code{\link{echart}} or \code{\link{echartR}}
#' @param show logical. Show the toolbox if TRUE. If you want to remove toolbox from
#' the echarts object, set it NULL.
#' @param language 'cn' or 'en', the language of the toolbox tooltips.
#' @param controls which widgets to show. Default \code{'mark', 'dataZoom', 'dataView', 'magicType',
#' 'restore', 'saveAsImage'}.
#' @param pos the clock-position of toolbox, refer to \code{\link{vecPos}}. Or you can
#' define a vector \code{c(x, y, orient)} yourself.#'
#' @param bgColor background color, default transparent ('rgba(0,0,0,0)').
#' @param borderColor border color, default '#ccc'.
#' @param borderWidth border width, default 0px (not shown).
#' @param padding Padding of the toolbox. Default 5px. Could also be a list assigning
#' padding of top, right, bottom and left.
#' @param itemGap Gap between the items. Default 10px.
#' @param itemSize Size of the items. Default 16px.
#' @param color Colors of the toolbox widgets (applied in loops). Default
#' c("#1e90ff", "#22bb22", "#4b0082", "#d2691e").
#' @param disableColor Color for disabled widgets. Default '#ddd'.
#' @param effectiveColor Color for widgets be triggered. Default 'red'.
#' @param showTitle Logical, if widgets title are shown. Default TRUE.
#' @param textStyle A list of the text style of the widgets. Default \code{
#' list(fontFamily=c('Arial, Verdana, sans-serif'), fontSize=12, fontStyle='normal',
#' fontWeight='normal')}
#' @param ... elipsis
#'
#' @return A modified echart object
#' @export
#'
#' @references \url{http://echarts.baidu.com/echarts2/doc/option.html#title~toolbox}
#' @examples
#' \dontrun{
#' g <- iris %>% echartR(x=Sepal.Width, y=Petal.Width, series=Species, type='scatter')
#' g %>% setToolbox(TRUE, 'en')
#' }
setToolbox <- function(chart, show=TRUE, language='cn',
                       controls=c('mark', 'dataZoom', 'dataView', 'magicType',
                                  'restore', 'saveAsImage'), pos=1,
                       bgColor='rgba(0,0,0,0)', borderColor='#ccc', borderWidth=0,
                       padding=5, itemGap=10, itemSize=16,
                       color=c("#1e90ff", "#22bb22", "#4b0082", "#d2691e"),
                       disableColor='#ddd', effectiveColor='red', showTitle=TRUE,
                       textStyle=NULL,
...){
    if (!inherits(chart, 'echarts'))
        stop('chart is not an Echarts object. ',
             'Check if you have missed a %>% in your pipe chain.')
    hasT <- 'timeline' %in% names(chart$x)
    if (hasT){
        if (is.null(show)) {
            chart$x$options[[1]]$toolbox <- NULL
            return(chart %>% tuneGrid())
        }
        type <- chart$x$options[[1]]$series[[1]]$type
        chart$x$options[[1]]$toolbox <- makeToolbox(
            toolbox=c(show, language), type, controls, pos, bgColor,
            borderColor, borderWidth, padding, itemGap, itemSize, color,
            disableColor, effectiveColor, showTitle, textStyle)
    }else{
        if (is.null(show)) {
            chart$x$toolbox <- NULL
            return(chart %>% tuneGrid())
        }
        type <- chart$x$series[[1]]$type
        chart$x$toolbox <- makeToolbox(
            toolbox=c(show, language), type, controls, pos, bgColor,
            borderColor, borderWidth, padding, itemGap, itemSize, color,
            disableColor, effectiveColor, showTitle, textStyle)
    }

    return(chart %>% tuneGrid())
}

#' @rdname setToolbox
#' @export
set_toolbox <- setToolbox

#' @rdname setToolbox
#' @export
setTB <- setToolbox

#' @rdname setToolbox
#' @export
set_tb <- setToolbox

makeDataZoom <- function(show=FALSE, pos=6, range=NULL, width=30,
                         fill='rgba(144,197,237,0.2)',
                         handle='rgba(70,130,180,0.8)',
                         bgColor = 'rgba(0,0,0,0)',
                         dataBgColor = '#eee', showDetail=TRUE, realtime=FALSE,
                         zoomLock=FALSE,
...){
    # Work function for setDataZoom
    if (is.numeric(pos[1])) pos <- vecPos(pos)
    if (!is.null(show)) {
        lstdataZoom <- list(show=show)
        if (! missing(fill)) if (fill != 'rgba(144,197,237,0.2)')
            lstdataZoom$fillerColor <- fill
        if (! missing(handle)) if (handle != 'rgba(70,130,180,0.8)')
            lstdataZoom$handleColor <- handle
        if (! missing(bgColor)) if (bgColor != 'rgba(0,0,0,0)')
            lstdataZoom$backgroundColor <- bgColor
        if (! missing(dataBgColor)) if (dataBgColor != '#eee')
            lstdataZoom$dataBackgroundColor <- dataBgColor
        if (! missing(showDetail)) if (!showDetail)
            lstdataZoom$showDetail <- showDetail
        if (! missing(realtime)) if (realtime)
            lstdataZoom$realtime <- realtime
        if (! missing(zoomLock)) if (zoomLock)
            lstdataZoom$zoomLock <- zoomLock

        if (pos[[3]] == 'vertical'){
            lstdataZoom[['y']] <- 60
            if (pos[[1]]=='left') lstdataZoom[['x']] <- 0
            if (pos[[1]]=='right') lstdataZoom[['x']] <- dev.size('px')[1] - 80
        }else{
            if (! (pos[[1]] == 'center' && pos[[2]] == 'bottom')){
                lstdataZoom[['x']] <- 80
                if (pos[[2]]=='top') lstdataZoom[['y']] <- 0
            }
        }

        lstdataZoom[['orient']] <- pos[[3]]
        if (lstdataZoom$orient == 'horizontal') lstdataZoom[['height']] <- width
        if (lstdataZoom$orient == 'vertical') lstdataZoom[['width']] <- width

        if (!is.null(range))
            range <- c(range[1], ifelse(length(range) == 1, range[1], range[2]))
        if (all(is.numeric(range[1:2]))){
            if (any(! range >= 0 | ! range <= 100)){
                stop("dataZoom should be between 0 and 100")
            }else{
                lstdataZoom[['start']] <- min(range[1:2])
                lstdataZoom[['end']] <- max(range[1:2])
            }
        }
    } else {
        lstdataZoom <- list(show=FALSE)
    }
    return(lstdataZoom)
}

#' Set \code{dataZoom} Bar of Echarts
#'
#' When an echart object is generated, you can modify it by setting dataZoom using
#' \code{\link{\%>\%}}.
#'
#' @param chart \code{echarts} object generated by \code{\link{echart}} or \code{\link{echartR}}
#' @param show logical. Show the dataZoom control if TRUE. If you want to remove
#' dataZoom from the echarts object, set it NULL.
#' @param pos the clock-position of dataZoom, refert to \code{\link{vecPos}}. You can
#' define a vector \code{c(x, y, orient)} yourself.
#' @param range A vector of \code{c(min, max)}. Cannot be out of the frame c(0, 100)
#' @param width The width of the dataZoom bar. Default 20px.
#' @param fill fillerColor of the dataZoom bar, in character \code{'rgba(red, green,
#' blue, alpha)'} format. Default 'rgba(144,197,237,0.2)' ("#90C5ED33").
#' @param handle handleColor of the dataZoom bar, in character \code{'rgba(red, green,
#' blue, alpha)'} format. Default 'rgba(70,130,180,0.8)' ("#4682B4CC").
#' @param bgColor background color. Default transparent ('rgba(0,0,0,0)')
#' @param dataBgColor background color of the data thumbnail (1st series). Default
#' '#eee'.
#' @param showDetail Logical, if show the details when zooming. Defaul TRUE.
#' @param realtime Logical, if realtime display the changes when zooming. Default FALSE.
#' @param zoomLock Logical, if the zoom range is locked. Deafult FALSE.
#' @param ... Elipsis
#' @return A modified echart object
#' @export
#'
#' @references \url{http://echarts.baidu.com/echarts2/doc/option.html#title~dataZoom}
#' @examples
#' \dontrun{
#' g <- iris %>% echartR(x=Sepal.Width, y=Petal.Width, series=Species, type='scatter')
#' g %>% setDataZoom(fill=rgba(c(col2rgb('gold'), 0.3)),
#'                   handle=rgba(c(col2rgb('gold'), 1)))
#'
#' g1 <- iris %>% echartR(x=Sepal.Width, y=Petal.Width, t=Species, type='scatter')
#' g1 %>% setDataZoom(fill=rgba(c(col2rgb('lightgreen'), 0.2)),
#'                   handle=rgba(c(col2rgb('darkgreen'), 0.5)))
#' }
setDataZoom <- function(chart, show=TRUE, pos=6, range=NULL, width=30,
                        fill='rgba(144,197,237,0.2)',
                        handle='rgba(70,130,180,0.8)', bgColor = 'rgba(0,0,0,0)',
                        dataBgColor = '#eee', showDetail=TRUE, realtime=FALSE,
                        zoomLock=FALSE, ...){
    if (!inherits(chart, 'echarts'))
        stop('chart is not an Echarts object. ',
             'Check if you have missed a %>% in your pipe chain.')
    hasT <- 'timeline' %in% names(chart$x)
    if (hasT){
        if (is.null(show)) {
            chart$x$options[[1]]$dataZoom <- NULL
            return(chart %>% tuneGrid())
        }
        chart$x$options[[1]][['dataZoom']] <- makeDataZoom(
            show=show, pos=pos, range=range, fill=fill, handle=handle,
            backgroundColor=bgColor, dataBackgroundColor=dataBgColor,
            showDetail=showDetail, realtime=realtime, zoomLock=zoomLock
        )
    }else{
        if (is.null(show)) {
            chart$x$dataZoom <- NULL
            return(chart %>% tuneGrid())
        }
        chart$x[['dataZoom']] <- makeDataZoom(
            show=show, pos=pos, range=range, fill=fill, handle=handle,
            backgroundColor=bgColor, dataBackgroundColor=dataBgColor,
            showDetail=showDetail, realtime=realtime, zoomLock=zoomLock)
    }

    return(chart %>% tuneGrid())
}

#' @rdname setDataZoom
#' @export
set_datazoom <- setDataZoom

#' @rdname setDataZoom
#' @export
set_dz <- setDataZoom

#' @rdname setDataZoom
#' @export
setDZ <- setDataZoom

makeDataRange <- function(show=FALSE, pos=8, min=NULL, max=NULL, splitNumber=5,
                          itemGap=5, labels=NULL, calculable=FALSE,
                          borderColor='#ccc', borderWidth=0,
                          selectedMode=list(TRUE, 'single', 'multiple'),
                          color=c("#1e90ff", "#f0ffff"),
                          splitList=NULL, initialRange=NULL,
...){
    # Work function for setDataRange
    ## color must be color vector
    ## splitList must be list(list(start=m, end=n, label=x, color=hex), ...)
    ## initialRange must be list(start=m, end=n)
    if (is.null(show)) {
        lstdataRange <- NULL
    } else {
        if (! show){
            lstdataRange <- NULL
        }else{
            if (splitNumber == 0) calculable <- TRUE
            if (calculable) splitNumber <- 0

            lstdataRange <- list(
                show=show, calculable=calculable,
                itemWidth=6, selectedMode=selectedMode[[1]]
            )

            if (is.numeric(pos[[1]]) && pos[[1]] <= 12){
                lstdataRange[c('x', 'y', 'orient')] <- vecPos(pos[[1]])
            }else if (length(pos)==3 && tolower(pos[[1]]) %in%
                      c('left', 'right', 'center') &&
                      tolower(pos[[2]]) %in% c('top', 'center', 'bottom') &&
                      tolower(pos[[3]]) %in% c('vertical', 'horizontal')){
                lstdataRange[c('x', 'y', 'orient')] <- pos
            }

            if (!missing(color)) lstdataRange[['color']] <- color
            if (!missing(borderColor)) lstdataRange[['borderColor']] <- borderColor
            if (!missing(borderWidth)) lstdataRange[['borderWidth']] <- borderWidth

            if (!missing(labels)) {
                if (length(labels) == 1) lstdataRange[['text']] <- c(labels, "")
                else lstdataRange[['text']] <- labels[1:2]
            }
            if (!missing(min)) lstdataRange[['min']] <- as.numeric(min)
            if (!missing(max)) lstdataRange[['max']] <- as.numeric(max)
            if (!missing(splitList)) {
                if (is.list(splitList) &&
                    all(names(splitList[[1]]) %in% c('start', 'end', 'label', 'color'))){
                    lstdataRange[['splitList']] <- splitList
                    lstdataRange[['itemGap']] <- itemGap
                }
            }else{
                if (splitNumber > 0) {
                    lstdataRange[['itemGap']] <- itemGap
                    lstdataRange[['splitNumber']] <- splitNumber
                }
            }
            if (calculable && !is.null(initialRange)){
                if (is.list(initialRange) &&
                    all(names(initialRange) %in% c('start', 'end'))){
                    lstdataRange[['range']] <- initialRange
                    lstdataRange$calculable <- TRUE
                }
            }
        }
    }
    return(lstdataRange)
}

#' Set \code{dataRange} Bar of Echarts
#'
#' When an echart object is generated, you can modify it by setting dataRange using
#' \code{\link{\%>\%}}.
#'
#' @param chart \code{echarts} object generated by \code{\link{echart}} or \code{\link{echartR}}
#' @param show logical. Show the dataRange control if TRUE. If you want to remove
#' dataRange from the echarts object, set it NULL.
#' @param pos the clock-position of dataRange, default 8. Refer to \code{\link{vecPos}}.
#' Or you can define the position vector \code{c(x, y, orient)} yourself.
#' @param valueRange The range of the dataRange bar in form of \code{c(min, max)}.
#' If NULL, echarts default will be used.
#' @param splitNumber How many discrete sections will the dataRange bar be divided into.
#' Default 5. Set it to 0 to set the bar continuous and calculable will be accordingly set TRUE.
#' @param itemGap The gap between itmes in pixels. Default 10px.
#' @param labels The labels to the ends the dataRange bar in form \code{c('high end',
#' 'low end')}. Default NULL, the min, max values will be used.
#' @param borderColor The border color of the dataRange bar. Default '#333'.
#' @param borderWidth The border width of the dataRange bar. Default 0px (not shown).
#' @param calculable Logical. If echart calculable feature is open. Default FALSE. If set
#' calculable TRUE, splitNumber will be set 0.
#' @param selectedMode The mode of the dataRange bar, default TRUE. You can also
#' set it 'single' or 'multiple'.
#' @param color The hex vector of colors used for dataRange bar. Default c("#1e90ff", "#f0ffff").
#' @param splitList A list for user-defined value split in the form of
#' \code{list(list(start=m, end=n, label=x, color=hex), ...)}. If a valid splitList is set,
#' splitNumber will be disabled.
#' @param initialRange Initial selected value range in the form of \code{list(start=m, end=n)}
#' @param ... Elipsis
#'
#' @return A modified echarts object
#' @export
#'
#' @references \url{http://echarts.baidu.com/echarts2/doc/option.html#title~dataRange}
#' @examples
#' \dontrun{
#' g <- iris %>% echartR(x=Sepal.Width, y=Petal.Width, series=Species, type='scatter')
#' g %>% setDataRange()
#'
#' g1 <- iris %>% echartR(x=Sepal.Width, y=Petal.Width, t=Species, type='scatter')
#' g1 %>% setDataRange(valueRange=c(0, 2.5))
#' }
setDataRange <- function(
    chart, show=TRUE, pos=8, valueRange=NULL, splitNumber=5, itemGap=5,
    labels=NULL, calculable=FALSE, borderColor='#ccc', borderWidth=0,
    selectedMode=list(TRUE, 'single', 'multiple'),
    color=c("#1e90ff", "#f0ffff"), splitList=NULL, initialRange=NULL,
...){
    if (!inherits(chart, 'echarts'))
        stop('chart is not an Echarts object. ',
             'Check if you have missed a %>% in your pipe chain.')
    hasT <- 'timeline' %in% names(chart$x)
    if (! is.null(valueRange[1])) {
        if (is.numeric(valueRange) && length(valueRange) > 1){
            min <- range(valueRange)[1]
            max <- range(valueRange)[2]
        }else{
            min <- max <- NULL
        }
    }else{
        min <- range(getYFromEChart(chart))[1]
        max <- range(getYFromEChart(chart))[2]
    }

    lst <- makeDataRange(
        show=show, pos=pos, min=min, max=max, splitNumber=splitNumber,
        itemGap=itemGap, labels=labels, calculable=calculable,
        borderColor=borderColor, borderWidth=borderWidth, selectedMode=selectedMode,
        color=color, splitList=splitList, initialRange=initialRange
    )
    if (!is.null(lst)){
        if (hasT){
            if (is.null(show)) {
                chart$x$options[[1]]$dataRange <- NULL
                return(chart %>% tuneGrid())
            }
            chart$x$options[[1]][['dataRange']] <- lst
        } else {
            if (is.null(show)) {
                chart$x$dataZoom <- NULL
                return(chart %>% tuneGrid())
            }
            chart$x[['dataRange']] <- lst
        }
    }

    return(chart %>% tuneGrid())
}

#' @rdname setDataRange
#' @export
set_datarange <- setDataRange

#' @rdname setDataRange
#' @export
set_dr <- setDataRange

#' @rdname setDataRange
#' @export
setDR <- setDataRange

#' Set \code{symbolList} of Echarts
#'
#' When an echart object is generated, you can modify it by setting symbolList using
#' \code{\link{\%>\%}}.
#' You can modify \code{symbolList} of the echarts object using this function. The symbolList
#' will be then mapped to data series in order.
#' @param chart \code{echarts} object generated by \code{\link{echart}} or \code{\link{echartR}}
#' @param symbols A vector assigning symbols. You can use an array of symbols.
#' If the the symbols array is shorter than the levels of the series in length,
#' the last symbol will be used to extend the array. Default NULL.
#' \itemize{
#'  \item If you set symbolList NULL or
#' leave it unset, the function circulates the default symbol list of Echarts:
#' \code{c('circle', 'rectangle', 'triangle', 'diamond', 'emptyCircle', 'emptyRectangle',
#' 'emptyTriangle', 'emptyDiamond')}.\cr
#'  \item You can also assign non-standard symbols,
#' such as \code{'heart', 'droplet', 'pin', 'arrow', 'star3', 'star4', 'star5', 'star6',
#' 'star7', 'star8', 'star9'}, etc.\cr
#'  \item When assigned to \strong{'none', no symbol will be shown}, you can thus yield lines
#' without points on it.  \cr
#'  \item The elements not in the above eligible symbols list will be eliminated.
#' }
#' @param ... Elipsis.
#'
#' @return A modified echarts object.
#' @export
#'
#' @references \url{http://echarts.baidu.com/echarts2/doc/option.html#title~symbolList}
#' @examples
#' \dontrun{
#' g <- iris %>% echartR(x=Sepal.Width, y=Petal.Width, series=Species)
#' g %>% setSymbols('emptycircle')
#' g %>% setSymbols(c('heart', 'pin'))
#' }
setSymbols <- function(chart, symbols=NULL, ...){
    if (!inherits(chart, 'echarts'))
        stop('chart is not an Echarts object. ',
             'Check if you have missed a %>% in your pipe chain.')
    hasT <- 'timeline' %in% names(chart$x)
    if (!is.null(symbols)){
        intersectSymbols <- symbols[tolower(symbols) %in% tolower(validSymbols)]
        idx <- sapply(intersectSymbols, function(x) {
            return(which(tolower(validSymbols) == tolower(x)))
        })
        symbolList <- as.list(validSymbols[unlist(idx)])
        if (length(symbolList) > 0) {
            if (hasT){
                nlvlseries <- sapply(chart$x$options, function(lst) {
                    length(lst$series)})
                nlvlseries <- max(unlist(nlvlseries))
                if (length(symbolList) < nlvlseries)
                    symbolList <- c(symbolList,
                                    rep(symbolList[length(symbolList)],
                                        nlvlseries-length(symbolList)))
                chart$x$options[[1]][['symbolList']] <- symbolList
            }else{
                nlvlseries <- length(chart$x$series)
                if (length(symbolList) < nlvlseries)
                    symbolList <- c(symbolList,
                                    rep(symbolList[length(symbolList)],
                                        nlvlseries-length(symbolList)))
                chart$x[['symbolList']] <- symbolList
            }
        }
    }
    return(chart)

}

#' @rdname setSymbols
#' @export
set_symbols <- setSymbols

#' @rdname setSymbols
#' @export
set_symbol <- setSymbols

#' @rdname setSymbols
#' @export
set_sl <- setSymbols

#' Set \code{roamController} of Echarts
#'
#' Set \code{roamController} of echarts object for map. you can modify it by setting symbolList using
#' \code{\link{\%>\%}}.
#' @param chart \code{echarts} object generated by \code{\link{echart}} or \code{\link{echartR}}
#' @param show Logical, if the widget is shown. If set NULL, the widget will be
#' removed from the chart object. Default TRUE.
#' @param pos Numeric 1-12 or vector \code{c(x, y, orient)}. Default 2. Note that
#' roamController is always vertical.
#' @param width Default 80.
#' @param height Default 120.
#' @param bgColor Background color, default 'rgba(0,0,0,0)' (transparent).
#' @param borderColor Border color, default '#ccc'.
#' @param borderWidth Border width, default 0px (not shown).
#' @param fillerColor Filler color, default '#fff'.
#' @param handleColor Handle color, default '#6495ed'.
#' @param step Movement in px per move. Default 15px.
#' @param mapTypeControl Which map to be controlled by \code{roamController}. E.g,,
#' \code{list(china=TRUE)}. Default NULL and \code{setRoam} will calculate automatically.
#' @param ... Elipsis
#'
#' @return A modified echarts object
#' @export
#'
#' @references \url{http://echarts.baidu.com/echarts2/doc/option.html#title~roamController}
#' @examples
#' \dontrun{
#' }
setRoam <- function(chart, show=TRUE, pos=2, width=80, height=120,
                    bgColor='rgba(0,0,0,0)', borderColor='#ccc', borderWidth=0,
                    fillerColor='#fff', handleColor='#6495ed', step=15,
                    mapTypeControl=NULL, ...){
    if (!inherits(chart, 'echarts'))
        stop('chart is not an Echarts object. ',
             'Check if you have missed a %>% in your pipe chain.')
    hasT <- 'timeline' %in% names(chart$x)
    if (is.null(show)){
        if (hasT)
            chart$x$options[[1]]$roamController <- NULL
        else
            chart$x$roamController <- NULL
    }else{
        lstRoam <- list(show=show, width=width, height=height,
                        backgroundColor=bgColor, borderColor=borderColor,
                        borderWidth=borderWidth, fillerColor=fillerColor,
                        handleColor=handleColor, step=step,
                        mapTypeControl=list(TRUE))
        if (length(pos)==1){
            pos <- vecPos(pos)
        }else stopifnot (length(pos) >= 3)
        lstRoam[c('x', 'y')] <- pos[1:2]

        if (is.null(mapTypeControl)){
            names(lstRoam$mapTypeControl) <- if (hasT)
                gsub("^(.*)|", "\\1", chart$x$options[[1]]$series[[1]]$mapType) else
                    gsub("^(.*)|", "\\1", chart$x$series[[1]]$mapType)
        }else if (is.list(mapTypeControl)){
            lstRoam$mapTypeControl <- mapTypeControl
        }else if (is.vector(mapTypeControl)){
            lstRoam$mapTypeControl <- rep(TRUE, length(mapTypeControl))
            names(lstRoam$mapTypeControl) <- mapTypeControl
        }

        if (hasT)
            chart$x$options[[1]]$roamController <- lstRoam
        else
            chart$x$roamController <- lstRoam
    }
    return(chart)
}

#' @rdname setRoam
#' @export
set_roam <- setRoam

#' @rdname setRoam
#' @export
set_rc <- setRoam

#' Set \code{legend} of Echarts
#'
#' When an echart object is generated, you can modify it by setting legend using
#' \code{\link{\%>\%}}.
#' You can modify the legend of the echarts object using this function.
#' @param chart \code{echarts} object generated by \code{\link{echart}} or \code{\link{echartR}}
#' @param show Logical. Show the legend or not. Default TRUE. If you want to remove
#' legend from the echarts object, set it NULL.
#' @param pos Clock position of the legend. Default 11. Refer to \code{\link{vecPos}}.
#' Or you can define the position vector \code{c(x, y, orient)} yourself.
#' @param selected A vector of series names that are selected on load. If you assign 'none',
#' then none of the series will be selected in the beginning.
#' @param selectedMode 'multiple' or 'single'. Default 'multiple'.
#' @param itemGap The gap between legend items. Default 5px.
#' @param borderColor The border color of the legend. Default '#ccc'.
#' @param borderWidth The border width of the legend. Default 0px (not shown).
#' @param textStyle A list of textStyle definition to decorate the text. E.g.,
#' \code{list(color='#444')} or \code{list(color='auto')}.
#' @param formatter A named formatter template or a string containing javascript codes.
#' E.g., \code{'{name}'}.
#' @param overideData A list of data to overide the legend text. E.g.,
#' \code{list(list(name='Series 1', icon='image://../asset/ico/favicon.png',
#' textStyle=list(color='#bbb')))}
#' @param ... Elipsis.
#'
#' @return A modified echarts object.
#' @export
#'
#' @references \url{http://echarts.baidu.com/echarts2/doc/option.html#title~legend}
#' @examples
#' \donrun{
#' # No timeline
#' g <- iris %>% echartR(x=Sepal.Width, y=Petal.Width, series=Species,
#'                       type='scatter')
#' g %>% setLegend(selected="versicolor")
#' g %>% setLegend(selected=levels(iris$Species)[1:2],
#'                 textStyle=textStyle(fontFamily='Times New Roman', color='purple',
#'                                fontWeight='bold', fontSize=16))
#'
#' # With Timeline
#' g1 <- iris %>% echartR(x=Sepal.Width, y=Petal.Width, t=Species, type='scatter')
#' g1 %>% setLegend(pos=12, selected='none',
#'                  textStyle=list(fontFamily='Courier New', fontSize=16))
#' }
setLegend <- function(
    chart, show=TRUE, pos=11, selected=NULL, selectedMode=c("multiple", "single"),
    itemGap=5, borderColor='#ccc', borderWidth=0, textStyle=list(color='auto'),
    formatter=NULL, overideData=NULL,
...){
    if (!inherits(chart, 'echarts'))
        stop('chart is not an Echarts object. ',
             'Check if you have missed a %>% in your pipe chain.')

    hasT <- 'timeline' %in% names(chart$x)
    series <- getSeriesPart(chart, 'category', fetch.all=TRUE)
    if (!is.null(dim(series))) series <- series[,1]
    series <- series[ifna(series, '') !='']
    # get series name vector from echarts object

    lstLegend <- list(show=show, data=as.list(unique(series)))

    if (is.numeric(pos[[1]]) && pos[[1]] <= 12){
        lstLegend[c('x', 'y', 'orient')] <- vecPos(pos[[1]])
    }else if (length(pos)==3 && tolower(pos[[1]]) %in%
              c('left', 'right', 'center') &&
              tolower(pos[[2]]) %in% c('top', 'center', 'bottom') &&
              tolower(pos[[3]]) %in% c('vertical', 'horizontal')){
        lstLegend[c('x', 'y', 'orient')] <- pos
    }

    selectedMode <- match.arg(selectedMode)
    lstLegend[['selectedMode']] <- selectedMode
    if (!is.null(selected)){
        unselected <- unique(series[! series %in% selected])
        lstLegend[['selected']] <- emptyList()
        for (item in unselected){
            lstLegend[['selected']][[item]] <- FALSE
        }
    }
    if (!missing(itemGap)) lstLegend[['itemGap']] <- itemGap
    if (!missing(borderColor)) lstLegend[['borderColor']] <- borderColor
    if (!missing(borderWidth)) lstLegend[['borderWidth']] <- borderWidth

    if (is.list(textStyle) && !identical(textStyle, list(color='auto')))
        if (all(names(textStyle) %in% c('fontFamily', 'color', 'fontSize',
                                        'fontStyle', 'fontWeight')))
            lstLegend[['textStyle']] <- textStyle
    if (!missing(formatter)) lstLegend[['formatter']] <- formatter
    if (!missing(overideData))
        if (all(names(overideData) %in% c('name', 'textStyle', 'icon')))
            lstLegend[['data']] <- overideData

    if (! is.null(show)) {
        if (hasT){
            if (is.null(show)) {
                chart$x$options[[1]]$legend <- NULL
                return(chart %>% tuneGrid())
            }
            chart$x$options[[1]][['legend']] <- lstLegend
        }else{
            if (is.null(show)) {
                chart$x$legend <- NULL
                return(chart %>% tuneGrid())
            }
            chart$x[['legend']] <- lstLegend
        }
    }

    return(chart %>% tuneGrid())
}

#' @rdname setLegend
#' @export
set_legend <- setLegend

autoPolar <- function(chart, type){
    if (!inherits(chart, 'echarts'))
        stop('chart is not an Echarts object. ',
             'Check if you have missed a %>% in your pipe chain.')
    chartTypes <- getSeriesPart(chart, 'type')
    hasT <- 'timeline' %in% names(chart$x)
    if (!all(chartTypes %in% c('radar'))) return(chart)

    # get chart meta data
    hasT <- 'timeline' %in% names(chart$x)
    if (hasT){
        data <- lapply(chart$x$options, function(l) getMeta(l))
        list.names <- names(data[[1]])
        data <- lapply(list.names, function(v) {
            do.call('rbind', lapply(data, function(l) l[[v]]))
        })
        names(data) <- list.names
    }else data <- getMeta(chart)
    dt <- data.frame(x=data$x[,1], y=data$y[,1])
    dt$idx <- if (is.null(data$facet)) 1 else data$facet[,1]
    index <- as.character(unique(dt$idx))
    dt$series <- if (is.null(data$series)) names(data$y)[1] else data$series[,1]
    if (!is.null(data$t)) {
        dt$t <- data$t[,1]
        dt <- data.table::dcast(dt, idx + x + series + t ~., sum, value.var='y')
        names(dt) <- c('idx', 'x', 'series', 't', 'y')
    }else{
        dt <- data.table::dcast(dt, idx + x + series ~., sum, value.var='y')
        names(dt) <- c('idx', 'x', 'series', 'y')
    }

    # layout
    layouts <- autoMultiPolarChartLayout(length(index), gap=1.5, top=10, bottom=10)
    rows <- layouts$rows
    cols <- layouts$cols
    centers <- layouts$centers
    rownames(centers) <- index
    radius <- layouts$radius

    # build polar lists
    obj <- lapply(index, function(i){
        dat <- dt[dt$idx == i,]
        o <- list(center=paste0(centers[i, 1:2], '%'), radius=paste0(radius, '%'))
        indicator <- lapply(as.character(unique(dat$x)), function(x){
            list(text=x, max=ifna(max(unname(dt[dt$x==x, 'y'])), 0) * 1.2)
        })
        o[['indicator']] <- indicator
        if (grepl('circle', type[which(index == i), 'misc']))
            o[['type']] <- 'circle'
        return(o)
    })

    if (hasT){
        chart$x$options[[1]][['polar']] <- obj
    }else{
        chart$x[['polar']] <- obj
    }

    return(chart)
}

#' Set \code{polar} of Echarts (For Radar Charts)
#'
#' Set the \code{polar} coordinates of Echarts for radar charts.  \cr
#' When an echart object is generated, you can modify it by setting aesthetics using
#' \code{\link{\%>\%}}.
#'
#' @param chart \code{echarts} object generated by \code{\link{echart}} or \code{
#' \link{echartR}}
#' @param polarIndex Integer vector. The index of the polar systems you want to set.
#' Default NULL.
#' @param center Vector of the x, y position of the polar center. Could be numeric
#' or character (percent form) vectors of length 2. Default c('50\%', '50\%').
#' @param radius The radius of the polar system, could be numeric or character (percent form).
#' Default '75\%'.
#' @param startAngle Numeric (-180 ~ 180). The start angle. Default 90.
#' @param splitNumber Numeric. The number of sections to divide. Default 5.
#' @param boundaryGap Numeric vector of length 2. The gapping policy of the axis.
#' Default c(0, 0).
#' @param scale Logical. Whether to ignore zero and zoom toward the range of _min and _max.
#' @param axisLine List. Axis line styles. You can set its \code{show, onZero, lineStyle}
#' features. Default \code{list(show=TRUE)}.
#' @param axisLabel List. Axis label styles. You can set its \code{show, rotate, margin,
#' clickable, formatter, textStyle} features. Default \code{list(show=FALSE)}.
#' @param splitLine List. Split line styles. You can set its \code{show, lineStyle}
#' features. Default \code{list(show=TRUE)}.
#' @param splitArea List. Split area styles. You can set its \code{show, onGap, areaStyle}
#' features. Default \code{list(show=TRUE)}.
#' @param type Character, 'polygon' or 'circle'. The type of the polar shape.
#' Default 'polygon'.
#' @param indicator List. The radar indicator and labels. The basic structure is \code{
#' list(list(text='...', min=..., max=..., axisLabel=list(...)), list(text='...', min=..., max=...),
#' list(...), ...)}. Default is empty.
#' @param axisName List. The name of the axis. You can set its \code{show, formatter,
#' textStyle} features. Default \code{list(show=TRUE, formatter=NULL, textStyle=
#' list(color='#333'))}.
#' @param ... Elipsis
#'
#' @return A modified echarts object
#' @export
#'
#' @references \url{http://echarts.baidu.com/echarts2/doc/option.html#title~polar}
#' @examples
#' \dontrun{
#' cars <- mtcars[c('Merc 450SE','Merc 450SL','Merc 450SLC'),
#'               c('mpg','disp','hp','qsec','wt','drat')]
#' cars$model <- rownames(cars)
#' cars <- data.table::melt(cars, id.vars='model')
#' names(cars) <- c('model', 'indicator', 'Parameter')
#' g <- echartr(cars, indicator, Parameter, model, type='radar') %>%
#'          setTitle('Merc 450SE  vs  450SL  vs  450SLC')
#' g %>% setPolar(c(1,3), type='circle') %>%
#'       setPolar(2, splitArea=list(show=FALSE)) %>%
#'       setPolar(3, axisName=list(textStyle=textStyle(color='red')))
#' }
#'
setPolar <- function(chart, polarIndex=NULL, center=c('50%', '50%'), radius='90%',
                     startAngle=90, splitNumber=5, boundaryGap=c(0, 0),
                     scale=FALSE, axisLine=NULL, axisLabel=NULL, splitLine=NULL,
                     splitArea=NULL, type=c('polygon', 'circle'),
                     indicator=NULL, axisName=NULL,
...){

    if (!inherits(chart, 'echarts'))
        stop('chart is not an Echarts object. ',
             'Check if you have missed a %>% in your pipe chain.')
    chartTypes <- getSeriesPart(chart, 'type')
    if (!all(chartTypes=='radar')) return(chart)

    hasT <- 'timeline' %in% names(chart$x)
    if (hasT){
        nIndex <- length(chart$x$options[[1]]$series)
    }else{
        nIndex <- length(chart$x$series)
    }

    if (is.null(polarIndex)) polarIndex <- 1:nIndex else
        if (!all(data.table::between(polarIndex, 1, nIndex)))
            stop(paste('polarIndex is not valid. It should all be between 1 and', nIndex))

    lstPolar <- list()
    if (!missing(center)) lstPolar[['center']] <- center
    if (!missing(radius)) lstPolar[['radius']] <- radius
    if (!missing(startAngle)) lstPolar[['startAngle']] <- startAngle
    if (!missing(splitNumber)) lstPolar[['splitNumber']] <- splitNumber
    if (!missing(boundaryGap)) lstPolar[['boundaryGap']] <- boundaryGap
    if (!missing(scale)) lstPolar[['scale']] <- scale
    if (!missing(axisLine)) lstPolar[['axisLine']] <- axisLine
    if (!missing(axisLabel)) lstPolar[['axisLabel']] <- axisLabel
    if (!missing(splitLine)) lstPolar[['splitLine']] <- splitLine
    if (!missing(splitArea)) lstPolar[['splitArea']] <- splitArea
    type <- match.arg(type)
    if (type != 'polygon') lstPolar[['type']] <- type
    if (!missing(indicator)) lstPolar[['indicator']] <- indicator
    if (!missing(axisName)) lstPolar[['name']] <- axisName

    for (i in polarIndex){
        if (hasT){
            chart$x$options[[1]]$polar[[i]] <- mergeList(
                chart$x$options[[1]]$polar[[i]], lstPolar)
        }else{
            chart$x$polar[[i]] <- mergeList(
                chart$x$polar[[i]], lstPolar)
        }
    }

    return(chart)
}

#' @rdname setPolar
#' @export
set_polar <- setPolar

#' Set Theme Or Misc Aesthetics (color, background, animation, border effects) of Echarts
#'
#' Set the entire theme of Echarts. You can use a pre-installed theme or define
#' you own themes. \cr \cr
#' You can also set aesthetics of Echarts separately, including \code{color, bgColor,
#' animation, calculable, borderColor, borderWidth}.\cr \cr
#' When an echart object is generated, you can modify it by setting aesthetics using
#' \code{\link{\%>\%}}.
#'
#' @section Self-defined Themes (UDT):
#' You can design your prefered themes using
#' \url{http://echarts.baidu.com/echarts2/doc/example/themeDesigner.html}.\cr
#' \cr The UDTs in \code{echarts} are JS objects. You can write it in a list form
#' and \code{setTheme} will parse it into JSON string for process. \cr \cr
#' A typical theme JS looks like: \cr
#' \code{{\cr
#'  color: ['#ff7f50','#87cefa','#da70d6','#32cd32','#6495ed'], \cr
#'  title: {x: 'left', y: 'top'}, \cr
#'  legend: {orient: 'horizontal'} \cr
#' }} \cr \cr
#' You should write in the format below: \cr
#' \code{list(color=c('#ff7f50','#87cefa','#da70d6','#32cd32','#6495ed'), \cr
#'  title=list(x='left', y='top'), legend=list(orient='horizontal'))}
#'
#' @param chart \code{echarts} object generated by \code{\link{echart}} or \code{
#' \link{echartR}}
#' @param theme \describe{
#' \item{Pre-installed themes}{\code{'default', 'macarons', 'infographic', 'blue',
#' 'dark', 'gray', 'green', 'helianthus', 'macarons2', 'mint', 'red', 'roma',
#' 'sakura', 'shine', 'vintage'}}
#' \item{User-defined themes}{E.g., \code{list(color=c('#ff7f50', '#87cefa',
#' '#da70d6','#32cd32','#6495ed'), \cr
#' title=list(x='left', y='top'), legend=list(orient='horizontal'))}}
#' }
#' @param palette name of the palette or a color vector. Default NULL to get echarts default.
#' It could be:
#' \describe{
#'  \item{\code{asis}}{keep the color palette applied in current echarts object}
#'  \item{Echarts theme palette}{"_default", "_macarons", "_infographic",
#'   "_blue", "_dark", "_gray", "_green", "_helianthus", "_macarons2", "_mint",
#'   "_red", "_roma", "_sakura", "_shine", "_vintage"}
#'  \item{\link{RColorBrewer} palettes}{Including 'BrBG', 'PiYG', 'PRGn', 'PuOr', 'RdBu',
#'  'RdGy', 'RdYlBu', 'RdYlGn', 'Spectral', 'Accent', 'Dark2', 'Paired', 'Pastel1',
#'  'Pastel2', 'Set1', 'Set2', 'Set3', 'Blues', 'BuGn', 'BuPu', 'GnBu', 'Greens',
#'  'Greys', 'Oranges', 'OrRd', 'PuBu', 'PuBuGn', 'PuRd', 'Purples', 'RdPu', 'Reds',
#'  'YlGn', 'YlGnBu', 'YlOrBr', 'YlOrRd'}
#'  \item{\link{ggthemes} palettes}{'calc', 'economist', 'economist_white', 'economist_stata',
#'  'excel', 'exel_fill', 'excel_line', 'excel_new', 'few', 'fivethirtyeight', '538', 'manyeyes',
#'  'gdocs', 'pander', 'tableau', 'stata', 'stata1','stata1r','statamono', 'ptol',
#'  'tableau20', 'tableau10medium', 'tableaugray', 'tableauprgy', 'tableaublrd',
#'  'tableaugnor', 'tableaucyclic', 'tableau10light', 'tableaublrd12', 'tableauprgy12',
#'  'tableaugnor12','hc','darkunica', 'solarized','solarized_red', 'solarized_yellow',
#'  'solarized_orange', 'solarized_magenta', 'solarized_violet', 'solarized_blue',
#'  'solarized_cyan', 'solarized_green', 'wsj', 'wsj_rgby', 'wsj_red_green',
#'  'wsj_black_green', 'wsj_dem_rep', 'colorblind', 'trafficlight'}
#'  \item{Aetna official palettes}{Including 'aetnagreen', 'aetnablue', 'aetnaviolet',
#'  'aetnaorange', 'aetnateal', 'aetnacranberry'}
#'  \item{Other palettes}{'rainbow', 'terrain', 'topo', 'heat', 'cm'}
#' }
#' \strong{Usage:} \cr
#' \itemize{
#'  \item If the value is not set, and the function defaults will be loaded \cr
#'  \item Set palette=NULL to use Echarts defaults \cr
#'  \item Set palette=palette name to assign any palette listed above \cr
#'  \item Set palette=\code{palette name(number)} to restrict number of colors within the
#'  palette (e.g., \code{palette='calc(3)'} picks 3 colors out of 'calc' \strong{RANDOMLY}) \cr
#'  \item Set palette=\code{c(color1, color2, color3, ...)} to define a palette vector,
#'  made of which either color names or Hex expressions. Use \code{\link{colors}()} to check
#'  available color names and check the effects using \code{demo(colors)}.
#' }
#' @param bgColor Color name/value of the background. Default is transparent
#' (\code{'rgba(0,0,0,0)'})
#' @param renderAsImage Logical. If TRUE, the interactive effects are disabled. Default FALSE.
#' @param calculable Logical. If TRUE, the chart is re-calculated after drag. Default FALSE.
#' @param calculableColor The border color of the tooltip during \code{calculable} effect.
#' Default 'rgba(255,165,0,0.6)'.
#' @param calculableHolderColor The color of \code{calculableHolder}. Default '#ccc'.
#' @param animation Logical. If TRUE, the animation is on at initiation. Default TRUE.
#' For IE8, it is recommended to set FALSE.
#' @param animationEasing The slight moving effect of major elements. Default 'ExponentialOut'.
#' Other choices: 'Linear', 'QuadraticIn', 'QuadraticOut', 'QuadraticInOut', 'CubicIn',
#' 'CubicOut', 'CubicInOut', 'QuarticIn', 'QuarticOut', 'QuarticInOut', 'QuinticIn',
#' 'QuinticOut', 'QuinticInOut', 'SinusoidalIn', 'SinusoidalOut', 'SinusoidalInOut',
#' 'ExponentialIn', 'ExponentialInOut', 'CircularIn', 'CircularOut', 'CircularInOut',
#' 'ElasticIn', 'ElasticOut', 'ElasticInOut', 'BackIn', 'BackOut', 'BackInOut',
#' 'BounceIn', 'BounceOut', 'BounceInOut'
#' @param animationDuration The duration into animation. Default 2000 (ms).
#' @param width Width (px) of the whole chart.
#' @param height Height (px) of the whole chart.
#' @param ... Elipsis
#'
#' @return A modified echarts object
#' @importFrom jsonlite toJSON
#' @export
#'
#' @seealso \code{\link{brewer.pal}}, \code{\link{colors}}
#' @references
#' \code{theme} \cr
#' \url{http://echarts.baidu.com/echarts2/doc/example/theme.html} \cr \cr
#' \code{backgroundColor} \cr
#' \url{http://echarts.baidu.com/echarts2/doc/option.html#title~backgroundColor} \cr \cr
#' \code{colors} \cr
#' \url{http://echarts.baidu.com/echarts2/doc/option.html#title~color} \cr
#' \url{http://colorbrewer2.org} \cr \cr
#' \code{other features} \cr
#' \url{http://echarts.baidu.com/echarts2/doc/option.html#title~renderAsImage} \cr
#' \url{http://echarts.baidu.com/echarts2/doc/option.html#title~calculable} \cr
#' \url{http://echarts.baidu.com/echarts2/doc/option.html#title~animation} \cr
#' \url{http://echarts.baidu.com/echarts2/doc/option.html#title~calculableColor} \cr
#' \url{http://echarts.baidu.com/echarts2/doc/option.html#title~calculableHolderColor}
#' @examples
#' \dontrun{
#' g <- echartR(iris, Sepal.Width, Petal.Width, series=Species)
#'
#' # Set themes
#' ## pre-installed themes
#' g %>% setTheme("infographic")
#' g %>% setTheme("blue")
#' g %>% setTheme("dark")
#' g %>% setTheme("gray")
#' g %>% setTheme("green")
#' g %>% setTheme("helianthus")
#' g %>% setTheme("macarons2")
#' g %>% setTheme("mint")
#' g %>% setTheme("red")
#' g %>% setTheme("sakura")
#' g %>% setTheme("shine")
#' g %>% setTheme("vintage")
#'
#' ## self-defined themes
#' theme <- list(color=c("#7AC143", "#7D3F98", "#F47721", "#D20962", "#00A78E",
#'               "#00BCE4", "#B8D936", "#EE3D94"), backgroundColor="#fef8ef")
#' g %>% setTheme(theme)
#'
#' ## Misc aethetics
#' g <- echartR(iris, x=Sepal.Width, y=Petal.Width, series=Species, type='scatter')
#' g %>% setTheme(palette='hc')
#' g %>% setTheme(palette=c('red', 'gold', 'deepskyblue'), 'gray95',
#'         animationHoldColor='red', animationEasing='CircularOut',
#'         animationDuration=10000)
#' }
setTheme <- function(
    chart, theme=c(
        'asis', 'default', 'macarons', 'infographic', 'blue', 'dark', 'gray', 'green',
        'helianthus', 'macarons2', 'mint', 'red', 'roma', 'sakura', 'shine', 'vintage'),
    palette='asis', bgColor=NULL, renderAsImage=FALSE, calculable=FALSE,
    calculableColor=NULL, calculableHolderColor=NULL, animation=TRUE,
    animationEasing=NULL, animationDuration=NULL, width=NULL, height=NULL,
    ...){
    if (!inherits(chart, 'echarts'))
        stop('chart is not an Echarts object. ',
             'Check if you have missed a %>% in your pipe chain.')

    ## pass theme in
    if (is.list(theme)){
        theme <- toJSON(theme)
    }else{
        theme <- match.arg(theme)
    }
    if (!identical(theme, 'asis')) chart$x[['theme']] <- theme
    if (missing(theme) || is.null(theme)) chart$x[['theme']] <- NULL

    hasT <- 'timeline' %in% names(chart$x)

    ## set colors
    ### get color
    if (is.list(theme)){
        colors <- if ('color' %in% names(theme)) theme$color else NULL
    }else{
        colors <- getColors(paste0(theme, 'theme'))
    }
    ### set color
    if (hasT){
        nSeries <- sapply(chart$x$options, function(lst) {
            return(length(lst$series))
        })
        nSeries <- max(unlist(nSeries))
        if (!missing(palette)) if (!identical(palette, 'asis')) {
            lstColor <- as.list(getColors(palette, n=nSeries))
            chart$x$options[[1]][['color']] <- lstColor[1:nSeries]
        }
        lsts <- chart$x$options
        lst <- chart$x$options[[1]]
    }else{
        nSeries <- length(getSeriesPart(chart, 'category', fetch.all=TRUE))
        if (nSeries == 0) nSeries <- 1
        if (!missing(palette)) if (!identical(palette, 'asis')) {
            lstColor <- as.list(getColors(palette, n=nSeries))
            chart$x[['color']] <- lstColor[1:nSeries]
        }
        lst <- chart$x
    }
    if (!missing(palette)) if (!identical(palette, 'asis'))
        colors <- getColors(palette)
    ### special chart type, special color setting
    ### if wordCloud, change itemStyle one by one
    if (any('wordCloud' %in% getSeriesPart(chart, 'type'))){
        if (hasT){
            for (iZ in seq_along(lsts)){
                vecS <- seq_along(lsts[[iZ]]$series)
                vWC <- sapply(vecS, function(i) lsts[[iZ]]$series[[i]]$type)
                iWC <- which(vWC=='wordCloud')
                if (length(iWC) >0)
                    for (iS in iWC) {
                        colorMeta <- getMeta(lsts[[iZ]]$series[[iS]]$data)
                        itemCol <- if (!is.null(colorMeta)){
                            colors <- rep(
                                colors, ceiling(nlevels(colorMeta)/length(colors)))
                            colors <- colors[1:nlevels(colorMeta)]
                            }else NULL
                        lapply(seq_along(lsts[[iZ]]$series[[iS]]$data),
                               function(i){
                            lsts[[iZ]]$series[[iS]]$data[[i]]$itemStyle$normal$color <<-
                                if (is.null(itemCol)) sample(colors, 1) else
                                    itemCol[as.numeric(colorMeta)[i]]
                        })
                        lst <- lsts[[1]]
                    }
            }
        }else{
            vecS <- seq_along(lst$series)
            vWC <- sapply(vecS, function(i) lst$series[[i]]$type)
            iWC <- which(vWC=='wordCloud')
            if (length(iWC) >0)
                lapply(iWC, function(iS){
                    colorMeta <- getMeta(lst$series[[iS]]$data)
                    itemCol <- if (!is.null(colorMeta)){
                        colors <- rep(
                            colors, ceiling(nlevels(colorMeta)/length(colors)))
                        colors <- colors[1:nlevels(colorMeta)]
                        }else NULL
                    lapply(seq_along(lst$series[[iS]]$data), function(i){
                        lst$series[[iS]]$data[[i]]$itemStyle$normal$color <<-
                            if (is.null(itemCol)) sample(colors, 1) else
                                itemCol[as.numeric(colorMeta[i])]
                    })
                })

        }
    }
    ## set the rest elements
    if (!missing(bgColor)) {
        lst[['backgroundColor']] <- ifelse(
            grepl("^rgba\\(", bgColor), bgColor, getColors(bgColor))
        if (grepl("^rgba\\(", bgColor)){
            backColor <- as.numeric(unlist(strsplit(bgColor,
                                                    "[\\(,\\)]"))[2:5])
            contrastColor <- c(rep(255, 4) - backColor)
            contrastColor <- paste0('rgba(', paste(contrastColor, collapse=","),
                                    ')')
        }else if (grepl("^#", bgColor)){
            cColor <- c(rep(255, 4) - col2rgb(bgColor, TRUE))
            contrastColor <- rgb(cColor[1], cColor[2], cColor[3], max=255)
        }
    }
    if (!missing(renderAsImage)) lst[['renderAsImage']] <- renderAsImage
    if (!missing(calculable)) lst[['calculable']] <- calculable
    if (!missing(calculableColor))
        lst[['calculableColor']] <- ifelse(
            grepl("^rgba\\(", calculableColor), calculableColor,
            getColors(calculableColor))
    if (!missing(calculableHolderColor))
        lst[['calculableHolderColor']] <- ifelse(
            grepl("^rgba\\(", calculableHolderColor), calculableHolderColor,
            getColors(calculableHolderColor))
    if (!missing(animation)) lst[['animation']] <- FALSE
    if (!missing(animationEasing))
        if (animationEasing %in%
            c('Linear', 'QuadraticIn', 'QuadraticOut', 'QuadraticInOut',
              'CubicIn', 'CubicOut', 'CubicInOut', 'QuarticIn', 'QuarticOut',
              'QuarticInOut', 'QuinticIn', 'QuinticOut', 'QuinticInOut',
              'SinusoidalIn', 'SinusoidalOut', 'SinusoidalInOut', 'ExponentialIn',
              'ExponentialInOut', 'CircularIn', 'CircularOut', 'CircularInOut',
              'ElasticIn', 'ElasticOut', 'ElasticInOut', 'BackIn', 'BackOut',
              'BackInOut', 'BounceIn', 'BounceOut', 'BounceInOut'))
            lst[['animationEasing']] <- animationEasing

    if (!missing(animationDuration)){
        lst[['animationDuration']] <- animationDuration
        if (hasT) chart$x$timeline[['playInterval']] <- animationDuration
    }

    ## merge list back to echarts object
    if (hasT) {
        lsts[[1]] <- lst
        chart$x$options <- lsts
    }else chart$x <- lst

    ## width and height
    if (!is.null(width)) chart[['width']] <- width
    if (!is.null(height)) chart[['height']] <- height

    return(chart %>% tuneGrid())
}

#' @rdname setTheme
#' @export
set_theme <- setTheme


makeTooltip <- function(type, trigger=NULL, formatter=NULL,
                        islandFormatter='{a} < br/>{b} : {c}',
                        position=NULL, enterable=FALSE, axisPointer=NULL,
                        textStyle=NULL, showDelay=20, hideDelay=100,
                        transitionDuration=0.4, bgColor='rgba(0,0,0,0.7)',
                        borderColor='#333', borderWidth=0, borderRadius=4,
                        show=TRUE, keepDefault=FALSE, timeIndex=NULL, ...){
    if (!ifnull(show, TRUE)) {
        lstTooltip = list(show=FALSE)
    }else{
        if (is.null(trigger)){
            trigger <- ifelse(
                type %in% c('pie', 'funnel', 'map', 'wordcloud', 'radar', 'chord',
                            'force', 'gauge', 'eventRiver', 'tree', 'treemap'),
                'item', 'axis')
        }else{
            trigger <- match.arg(trigger, c('item', 'axis'))
        }

        lstTooltip <- list(show = ifnull(show, TRUE), trigger = trigger)

        ## fetch default features
        lstTooltip[c('axisPointer', 'textStyle')] <-
            list(list(
                type=ifelse(type %in% c('line'), 'line',
                            ifelse(type %in% c('bar'), 'shadow',
                                ifelse(type %in% c('scatter', 'map', 'heatmap'),
                                       'cross', 'none'))),
                crossStyle=list(type='dashed'),
                lineStyle=list(type='solid', width=1),
                shadowStyle=list(color='rgba(150,150,150,0.3)', width='auto',
                                 type='default')
            ),
            list(color='#fff')
            )
        if (keepDefault){
            lstTooltip[c('islandFormatter', 'enterable', 'showDelay', 'hideDelay',
                         'transitionDuration', 'backgroundColor', 'borderWidth')] <-
                list('{a} < br/>{b} : {c}', FALSE, 20, 100, 0.4, '#333', 0)
        }
        ## fetch features
        if (!is.null(position)) lstTooltip[['position']] <- position
        if (!is.null(formatter)) lstTooltip[['formatter']] <- formatter
        if (!is.null(islandFormatter))
            lstTooltip[['islandFormatter']] <- islandFormatter
        if (!is.null(enterable)) lstTooltip[['enterable']] <- enterable
        if (!is.null(axisPointer)) {
            if (all(names(axisPointer) %in% c('type', 'lineStyle', 'crossStyle',
                                              'shadowStyle', 'textStyle'))){
                lstTooltip[['axisPointer']] <- axisPointer
            }
            else warning(paste(
                "axisPointer must be a list containing any of the below:\n",
                "'type', 'lineStyle', 'crossStyle', 'shadowStyle', 'textStyle'."))
        }

        if (!is.null(textStyle)) {
            if (all(names(textStyle) %in% c(
                'color', 'decoration', 'align', 'baseline', 'fontFamily',
                'fontSize', 'fontStyle', 'fontWeight')))
                lstTooltip[['textStyle']] <- textStyle
            else warning(paste(
                "textStyle must be a list containing any of the below:\n",
                "'color', 'decoration', 'align', 'baseline', 'fontFamily',",
                "'fontSize','fontStyle', 'fontWeight'."))
        }
        if (!is.null(showDelay)) lstTooltip[['showDelay']] <- showDelay
        if (!is.null(hideDelay)) lstTooltip[['hideDelay']] <- hideDelay
        if (!is.null(transitionDuration))
            lstTooltip[['transitionDuration']] <- transitionDuration
        if (!is.null(bgColor))
            lstTooltip[['backgroundColor']] <- getColors(bgColor)[1]
        if (!is.null(borderColor))
            lstTooltip[['borderColor']] <- getColors(borderColor)[1]
        if (!is.null(borderWidth)) lstTooltip[['borderWidth']] <- borderWidth
        if (!is.null(borderRadius))
            lstTooltip[['borderRadius']] <- borderRadius
    }
    return(lstTooltip)
}

determineFormatter <- function(type){
    if (type %in% c('scatter')){
        formatter <- tooltipJS('scatter')
    }else if (type %in% c('scatter_time')){
        formatter <- tooltipJS('scatter_time')
    }else if (type %in% c('pie')){
        formatter <- tooltipJS('pie')
    }else if (type %in% c('pie')){
        formatter <- tooltipJS('pie')
    }else if (type %in% c('chord', 'force')){
        # if (length(getSeriesPart(chart, 'name')) == 1){
        #     formatter <- tooltipJS('chord_mono')
        # }else{
            formatter <- tooltipJS('chord_multi')
        # }
    }else if (type == 'k'){
        formatter <- tooltipJS('k')
    }else if (type %in% c('bar', 'line')){
        formatter <- tooltipJS('line')
    }else if (type %in% c('bar_time', 'line_time')){
        formatter <- tooltipJS('line_time')
    }else if (type == 'hist'){
        formatter <- tooltipJS('hist')
    }else{
        formatter <- NULL  # use default
    }
    return(formatter)
}

#' Set \code{tooltip} of Echarts
#'
#' Set tooltip of Echarts, at various levels (entire chart, specific series) with
#' various formats. \cr
#' When an echart object is generated, you can modify it by setting tooltip using
#' \code{\link{\%>\%}}.
#' @note Note that the \code{tooltip} feature is inheritable in terms of timeline.
#' \code{setTooltip} automatically breaks the inheritability by resetting tooltip
#' formats in the timeslots following the timeslots whose tooltip format are changed.
#' .
#' @param chart \code{echarts} object generated by \code{\link{echart}} or \code{\link{echartR}}
#' @param series A vector of series indices or names. e.g., \code{c('setosa', 'virginica')}
#' or \code{1:2}
#' @param timeslots A vector of time slices indices or names, e.g., \code{c(1990, 1992)}
#'  or \code{c(1,3)}. You can also use \code{t} as a short form of \code{timeslots}.
#' @param trigger Type of trigger, \code{'item'}, or \code{'axis'}.
#' @param formatter The format of the tooltip content.
#' \describe{
#'  \item{string(template)}{
#'   \itemize{
#'   \item \{a\} | \{a0\}
#'   \item \{b\} | \{b0\}
#'   \item \{c\} | \{c0\}
#'   \item \{d\} | \{d0\} (not applicable for some types)
#'   \item \{e\} | \{e0\} (not applicable for some types)
#'   }}
#'  \item{function}{the JS list is in the form \code{[params, ticket, callback]}.}
#'  \item{'none'}{set it NULL.}
#' }
#' \strong{Meanings of \{a\}, \{b\}, \{c\}, \{d\}...}
#' \tabular{ll}{
#'  line, bar, k \tab \code{a} (series name), \code{b} (category name), \code{c} (value) \cr
#'  scatter \tab \code{a} (series name), \code{b} (data name), \code{c} (value array) \cr
#'  map \tab\code{a}(series name), \code{b} (area name), \code{c} (combined value) \cr
#'  pie, radar, gauge, funnel \tab \code{a} (series names), \code{b}(data item name),
#'    \code{c} (value), \code{d} (pie:percent|radar:indicator) \cr
#'  force, chord \tab \itemize{
#'    \item nodes: \code{a} (series name), \code{b} (node name), \code{c} (node value),
#'      \code{d} (node type index);
#'    \item links: \code{a} (series name), \code{b} (link name), \code{c} (link value),
#'      \code{d} (big node name/index), \code{e} (small node/index)
#'    }
#' }
#' \strong{JS Function Param Template...}
#' \tabular{ll}{
#'  \code{seriesIndex} \tab 0, 1, 2, ... \cr
#'  \code{seriesName} \tab 'Monday', 'Tuesday', ... \cr
#'  \code{name} \tab 'day1', 'day2', ... \cr
#'  \code{dataIndex} \tab 0, 1, 2, ... \cr
#'  \code{data} \tab data \cr
#'  \code{value} \tab value \cr
#'  \code{percent} \tab special  //pie \cr
#'  \code{indicator} \tab special  //radar, force, chord \cr
#'  \code{value2} \tab special2  //force, chord \cr
#'  \code{indicator2} \tab special2  //force, chord
#' }
#' @param islandFormatter Formatter of data island for calcualable effect. Can be
#' string (default \code{'{a} <br/>{b} : {c}'}) or JS function.
#' @param position Can be fixed position array \code{c(x, y)} or a JS function, e.g.,
#' \code{JS('function([x, y]) {return [newX, newY]}')}. Default NULL.
#' @param enterable If users can click into the tooltip for interacions. Default FALSE.
#' @param axisPointer The pointer formatter of axis. Default is a list \code{
#' list(type = "line", \cr
#'      lineStyle = list(color = "#48b", width = 2, type = "solid"), \cr
#'      crossStyle = list(color = "#1e90ff", width = 1, type = "dashed"), \cr
#'      shadowStyle = list(color = "rgba(150,150,150,0.3)", width = "auto", type = "default") \cr
#' )}.
#' @param textStyle text style of the tooltip. In a list form. Default \code{list(color
#' ="#fff")}. The list coud contain elements of \code{color, decoration, fontSize,
#' fontFamily, fontStyle, fontWeight, align, baseline}.
#' @param showDelay Delayed time at show (ms). Default 20ms.
#' @param hideDelay Delayed time at hide (ms). Default 100ms.
#' @param transitionDuration The time spent at animation exchange. Default 0.4. Set
#' if 0 if you want real-time interaction.
#' @param bgColor Background color of tooltips. Default 'rgba(0,0,0,0.7)' (
#' semi-transparent dark gray).
#' @param borderColor Borderline color of the tooltips. Default '#333'.
#' @param borderWidth Border width of the tooltips. Default 0 (not shown).
#' @param borderRadius Border radius of the tooltips. Default 4px.
#' @param show Logical. If the tooltips are shown. Default TRUE.
#' @param ... Elipsis
#'
#' @return A modified echarts object
#' @export
#'
#' @references \url{http://echarts.baidu.com/echarts2/doc/option.html#title~tooltip}
#' @examples
#' \dontrun{
#' g <- echartR(iris, Sepal.Width, Petal.Width, series=Species)
#'
#' ## global tooltip
#' g %>% setTooltip(trigger='item', bgColor='rgba(0,0,200,0.7)')
#'
#' ## series-specific tooltip
#' bg <- sapply(c('orange', 'deepskyblue', 'violet'), col2rgb)
#' bg <- rbind(bg, 0.7)  # extend the color matrix with alpha 0.7
#' bgCol <- unname(apply(bg, 2, rgba))  # get rgba colors with alpha
#' g %>% setTooltip(series='setosa', bgColor=bgCol[1]) %>%
#'       setTooltip(series=2, bgColor=bgCol[2]) %>%
#'       setTooltip(series=3, bgColor=bgCol[3])
#'
#' ## series-and-timeline-specific tooltip
#' bg <- sapply(c('orange', 'deepskyblue', 'violet'), col2rgb)
#' bg <- rbind(bg, 0.7)  # extend the color matrix with alpha 0.7
#' bgCol <- unname(apply(bg, 2, rgba))  # get rgba colors with alpha
#' iris$tag <- 1 + as.integer(row.names(iris)) %% 3
#' iris <- iris[order(iris$tag),]
#' g1 <- echartR(iris, Sepal.Width, Petal.Width, series=Species, t=tag)
#' g1 %>% setTooltip(series='setosa', bgColor=bgCol[1]) %>%
#'        setTooltip(series=2, bgColor=bgCol[2]) %>%
#'        setTooltip(series=3, bgColor=bgCol[3]) %>%
#'        setTooltip(t=1, borderColor='red', borderWidth=3) %>%
#'        setTooltip(t=2, borderColor='gold', borderWidth=3) %>%
#'        setTooltip(t=3, borderColor='green', borderWidth=3)
#'
#' g1 %>% setTooltip(series=2, t=2, bgColor='blue')
#' # tooltip format of series 2 in the 2nd timeslot is changed to bg blue,
#' # while it in the 3rd timeslot is reset to default
#' }
setTooltip <- function(chart, series=NULL, timeslots=NULL, trigger=NULL,
                       formatter=NULL, islandFormatter='{a} < br/>{b} : {c}',
                       position=NULL, enterable=FALSE, axisPointer=NULL, textStyle=NULL,
                       showDelay=20, hideDelay=100, transitionDuration=0.4,
                       bgColor='rgba(0,0,0,0.7)', borderColor='#333',
                       borderWidth=0, borderRadius=4, show=TRUE,
                       ...){
    if (!inherits(chart, 'echarts'))
        stop('chart is not an Echarts object. ',
             'Check if you have missed a %>% in your pipe chain.')
    if ('t' %in% names(list(...))) timeslots <- list(...)[['t']]

    chartTypes <- getSeriesPart(chart, 'type')
    if ('eventRiver' %in% chartTypes) enterable <- TRUE
    if (is.null(dim(chartTypes))) chartTypes <- as.matrix(chartTypes, ncol=1)
    uniSeries <- getSeriesPart(chart, 'category', fetch.all=TRUE)
    if (!is.null(dim(uniSeries))) uniSeries <- uniSeries[,1]

    hasT <- 'timeline' %in% names(chart$x)
    timeXAxis <- FALSE
    if (! hasT) {
        timeslots <- NULL
        if ('xAxis' %in% names(chart$x)) if (chart$x$xAxis[[1]]$type == 'time'){
            trigger <- 'item'
            timeXAxis <- TRUE
        }
    }else{
        timeslotsIndex <- seq_len(length(chart$x$timeline$data))
        if (!is.null(timeslots)){
            if (all(is.numeric(timeslots))){
                vecZ <- timeslots[timeslots %in% timeslotsIndex]
            }else{
                vecZ <- which(timeslotsIndex %in% timeslots)
            }
        }
        if ('xAxis' %in% names(chart$x$options[[1]]))
            if (chart$x$options[[1]]$xAxis[[1]]$type == 'time') {
                trigger <- 'item'
                timeXAxis <- TRUE
            }
    }
    if (length(chartTypes) == 1) {
        series <- NULL
    }else{
        seriesIndex <- seq_len(length(uniSeries))
        if (!is.null(series)){
            if (all(is.numeric(series))){
                vecS <- series[series %in% seriesIndex]
            }else{
                vecS <- which(uniSeries %in% series)
            }
        }
    }

    setAlongSZ <- c(!is.null(series), !is.null(timeslots))
    ## By default, do not set tooltip along series or t
    if (ifnull(show, TRUE)){
        if (hasT){
            if (is.null(chart$x$options[[1]][['tooltip']]))
                chart$x$options[[1]][['tooltip']] <- list(show=TRUE)
            else
                chart$x$options[[1]][['tooltip']][['show']]=TRUE
        }else{
            if (is.null(chart$x[[1]][['tooltip']]))
                chart$x[['tooltip']] <- list(show=TRUE)
            else
                chart$x[['tooltip']][['show']]=TRUE
        }
    }

    fixedPart <- paste0(
        "makeTooltip(",
        "trigger=trigger, islandFormatter=islandFormatter, position=position, ",
        "enterable=enterable, axisPointer=axisPointer, textStyle=textStyle, ",
        "showDelay=showDelay, hideDelay=hideDelay, ",
        "transitionDuration=transitionDuration, ",
        "bgColor=bgColor, borderColor=borderColor, ",
        "borderWidth=borderWidth, borderRadius=borderRadius, ",
        "show=show, formatter=if (ifnull(formatter, '')=='none') NULL else ",
        "determineFormatter('")

    defaultPart <- "makeTooltip(keepDefault=TRUE, type='"

    if (identical(setAlongSZ, c(FALSE, FALSE))){  # global set
        lhs <- ifelse(hasT, "chart$x$options[[1]][['tooltip']]",
                      "chart$x[['tooltip']]")
        rhs <- paste0(fixedPart, chartTypes[[1]],
                      ifelse(timeXAxis, "_time'", "'"), "), type='",
                      chartTypes[[1]], "')")

    }else if (identical(setAlongSZ, c(TRUE, FALSE))){  # set along series
        if (hasT) lhs <- paste0("chart$x$options[[1]]$series[[",
                                vecS, "]][['tooltip']]")
        else lhs <- paste0("chart$x$series[[", vecS, "]][['tooltip']]")
        rhs <- paste0(fixedPart, chartTypes[vecS, 1],
                      ifelse(timeXAxis, "_time'", "'"), "), type='",
                      chartTypes[vecS, 1], "')")

    }else if (identical(setAlongSZ, c(FALSE, TRUE))){  # set along timeline
        if (hasT) {  # if not hasT, this senario fails
            lhs <- paste0("chart$x$options[[", vecZ, "]][['tooltip']]")
            rhs <- paste0(fixedPart, chartTypes[1, vecZ],
                          ifelse(timeXAxis, "_time'", "'"), "'), type='",
                          chartTypes[1, vecZ], "')")
            # the following item to vecZ be reset to default
            vecZ1 <- vecZ + 1
            if (any(vecZ1 > length(chart$x$timeline$data))){
                vecZ1[length(vecZ1)] <- min(timeslotsIndex[!timeslotsIndex %in%
                                                               c(vecZ, vecZ1)])
            }
            lhs1 <- paste0("chart$x$options[[", vecZ1, "]][['tooltip']]")
            rhs1 <- paste0(defaultPart, chartTypes[1, vecZ1], "')")
            lhs <- c(lhs, lhs1)
            rhs <- c(rhs, rhs1)
        }

    }else{  # set along s and t
        vecZS <- as.matrix(expand.grid(vecZ, vecS))
        vecZ1 <- vecZ + 1
        if (any(vecZ1 > length(chart$x$timeline$data))){
            vecZ1[length(vecZ1)] <- min(timeslotsIndex[!timeslotsIndex %in%
                                                           c(vecZ, vecZ1)])
        }
        vecZS1 <- as.matrix(expand.grid(vecZ1, vecS))
        if (hasT) {
            lhs <- paste0("chart$x$options[[", vecZS[,1], "]]$series[[",
                          vecZS[,2], "]][['tooltip']]")
            lhs1 <- paste0("chart$x$options[[", vecZS1[,1], "]]$series[[",
                           vecZS1[,2], "]][['tooltip']]")
        }else{
            lhs <- paste0("chart$x[[", vecZS[,1], "]]$series[[", vecZS[,2],
                          "]][['tooltip']]")
            lhs1 <- paste0("chart$x[[", vecZS1[,1], "]]$series[[", vecZS1[,2],
                           "]][['tooltip']]")
        }

        rhs <- paste0(fixedPart, chartTypes[vecZS],
                      ifelse(timeXAxis, "_time'", "'"), "'), type='",
                      chartTypes[vecZS], "')")
        rhs1 <- paste0(defaultPart, chartTypes[vecZS1], "')")

        lhs <- c(lhs, lhs1)
        rhs <- c(rhs, rhs1)
    }

    eval(parse(text=paste(lhs, "<-", rhs)))
    if (class(formatter) %in% c("JS_EVAL", "character"))
        if (formatter != 'none') chart$x$tooltip$formatter <- formatter

    return(chart)
}

#' @export
#' @rdname setTooltip
setTT <- setTooltip

#' @export
#' @rdname setTooltip
set_tt <- setTooltip

#' @export
#' @rdname setTooltip
set_tooltip <- setTooltip

#' Set \code{timeline} of Echarts
#'
#' Set timeline of Echarts when the echarts object contains timeline slices (t). \cr
#' When an echart object is generated, you can modify it by setting tooltip using
#' \code{\link{\%>\%}}.
#'
#' @param chart \code{echarts} object generated by \code{\link{echart}} or \code{\link{echartR}}
#' @param show Logical. If or not the timeline is shown. Default TRUE. If you want
#' to remove timeline from the echarts object, set if NULL.
#' @param type 'time' or 'number' format of the timeline. Default 'time'.
#' @param realtime Logical. If or not the changes take effect in realtime manner.
#' Default TRUE.
#' @param x x coordinate of the upper left point of the timeline bar. Default 80.
#' @param y y coordinate of the upper left point of the timeline bar. Default NULL
#' (automatic).
#' @param x2 x coordinate of the upper left point of the timeline bar. Default 80.
#' @param y2 y coordinate of the upper left point of the timeline bar. Default 0.
#' @param width Width of the timeline. Could be a number or a character in percent
#' form. Default NULL.
#' @param height Height of the timeline. Default 50 (px).
#' @param bgColor Background color of the timeline. Default "rgba(0,0,0,0)" (transparent).
#' @param borderColor Border color of the timeline. Default "#ccc".
#' @param borderWidth Border width of the timeline. Default 0 (px) (not shown).
#' @param controlPosition Position of the control of the timeline. Could be 'left',
#' 'right' or 'none'. Default 'left'.
#' @param autoPlay Logical. If or not the timeline auto displays. Default FALSE.
#' @param loop Logical. If or not the timeline displays in loop mode. Default TRUE.
#' @param playInterval Interval when displays each timeslice. Default 2000 (ms).
#' @param lineStyle A list. Line style of the timeline. Default value: \cr
#' \code{list(color="#666", width=1, type="dashed")}. \cr Supports features of \code{'color',
#' 'width', 'type', 'shadowColor', 'shadowBlur', 'shadowOffsetX', 'shadowOffsetY'}
#' @param label A list. Label style of the timeline. Default value: \cr
#' \code{list(show=TRUE, interval="auto", rotate=0, formatter=NULL, \cr
#' textStyle=list(color="#333"))}. \cr Supports features of \code{'show', 'interval',
#'  'rotate', 'formatter', 'textStyle'}.
#' @param checkpointStyle A list. Checkpoint style of the timeline. Default value: \cr
#' \code{list(symbol="auto", symbolSize="auto", color="auto", \cr
#' borderColor="auto", borderWidth="auto", \cr
#' label=list(show=FALSE, textStyle=list(color="auto")))}. \cr
#' Supports features of \code{'symbol', 'symbolSize', 'color', 'borderColor',
#' 'borderWidth', 'label'}.
#' @param controlStyle A list. Control style of the timeline. Default value: \cr
#' \code{list(itemSize=15, itemGap=5, normal=list(color="#333"),
#' emphasis=list(color="#1e90ff"))}. \cr
#' Supports features of \code{'itemSize', 'itemGap', 'normal', 'emphasis'}.
#' @param symbol Character. The symbol used in timeline. Default 'emptyDiamond'.
#' You can use symbols in \code{\link{setSymbols}}.
#' @param symbolSize The size of the symbols. Default 4.
#' @param currentIndex The current index position, in correspondance with \code{t}.
#' It is used to show specific timeline slices. Default 0.
#' @param data The data list of the timeline, also used as timeline data label.
#' Default NULL.
#' @param ... Elipsis
#'
#' @return A modified echarts object
#' @export
#'
#' @references \url{http://echarts.baidu.com/echarts2/doc/option.html#title~timeline}
#' @examples
#' \dontrun{
#' ## type = "number"
#' iris$tag <- 1 + as.integer(row.names(iris)) %% 3
#' iris <- iris[order(iris$tag),]
#' g <- echartR(iris, Sepal.Width, Petal.Width, series=Species, t=tag)
#' g %>% setTimeline(y2=30, symbol='emptyCircle',
#'                   autoPlay=FALSE, data=c('tag 1', 'tag 2', 'tag 3'))
#'
#' ## type = "time"
#' ### You have to use a js function to reformat the label if you want to change
#' ### the label text
#' iris$date <- as.Date(paste0("2013-1-", 1 + as.integer(row.names(iris)) %% 5))
#' iris <- iris[order(iris$date),]
#' g <- echartR(iris, Sepal.Width, Petal.Width, series=Species, t=date)
#' g %>% setTimeline(y2=30, symbol='emptyCircle', autoPlay=FALSE, label=list(
#'       formatter=JS(paste('function(s) {return s.slice(8,10) + "\u65e5";}'))))
#' }
#'
setTimeline <- function(chart, show=TRUE, type=c('time', 'number'), realtime=TRUE,
                        x=80, y=NULL, x2=80, y2=0, width=NULL, height=50,
                        bgColor='rgba(0,0,0,0)', borderColor='#ccc',
                        borderWidth=0, controlPosition=c('left', 'right', 'none'),
                        autoPlay=FALSE, loop=TRUE, playInterval=2000,
                        lineStyle=NULL, label=NULL, checkpointStyle=NULL,
                        controlStyle=NULL, symbol='emptyDiamond', symbolSize=4,
                        currentIndex=0, data=NULL,
                        ...){
    if (!inherits(chart, 'echarts'))
        stop('chart is not an Echarts object. ',
             'Check if you have missed a %>% in your pipe chain.')
    if (! 'timeline' %in% names(chart$x)) return(chart)
    else if (is.null(show)) {
        chart$x$timeline <- NULL
        return(chart %>% tuneGrid())
    }
    lst <- chart$x$timeline
    type <- match.arg(type)
    if (inherits(getMeta(chart$x$options[[1]])$t[,1],
                 c("Date", "POSIXct", "POSIXlt"))){
        type <- 'time'
    }else{
        type <- 'number'
    }

    controlPosition <- match.arg(controlPosition)
    if (! tolower(symbol) %in% tolower(validSymbols)){
        symbol <- 'emptyDiamond'
    }else{
        symbol <- validSymbols[which(tolower(validSymbols) %in% tolower(symbol))]
    }

    ## default params
    if (! ifnull(show, TRUE)) lst$show <- show
    if (! ifnull(type, 'time') == 'time') lst$type <- type
    if (! ifnull(realtime, TRUE)) lst$realtime <- realtime
    if (! ifnull(x, 80) == 80) lst$x <- x
    if (! is.null(y)) lst$y <- y
    if (! ifnull(x2, 80) == 80) lst$x2 <- x2
    if (! ifnull(y2, 0) == 0) lst$y2 <- y2
    if (! is.null(width)) lst$width <- width
    if (! ifnull(height, 50) == 50) lst$height <- height
    if (! ifnull(bgColor, 'rgba(0,0,0,0)') == 'rgba(0,0,0,0)')
        lst$backgroundColor <- getColors(bgColor)
    if (! ifnull(borderColor, '#ccc') == '#ccc') lst$borderColor <- borderColor
    if (! ifnull(borderWidth, 0) == 0) lst$borderWidth <- borderWidth
    if (! ifnull(controlPosition, 'left') == 'left')
        lst$controlPosition <- controlPosition
    if (ifnull(autoPlay, FALSE)) lst$autoPlay <- autoPlay
    if (! ifnull(loop, TRUE)) lst$loop <- loop
    if (! ifnull(playInterval, 2000) == 2000) lst$playInterval <- playInterval

    if (! identical(ifnull(lineStyle, list(color="#666", width=1, type="dashed")),
                    list(color="#666", width=1, type="dashed"))){
        validLineStyleFeature <- c('color', 'width', 'type', 'shadowColor',
                                   'shadowBlur', 'shadowOffsetX', 'shadowOffsetY')
        if (! all(names(lineStyle) %in% validLineStyleFeature))
            stop(paste("Only supports lineStyle features as below:\n",
                       validLineStyleFeature))
        lst$lineStyle <- lineStyle
    }

    defaultLabel <- list(show=TRUE, interval="auto", rotate=0,
                         formatter=NULL, textStyle=list(color="#333"))
    if (! identical(ifnull(label, defaultLabel), defaultLabel)){
        validLabelFeature <- c('show', 'interval', 'rotate', 'formatter',
                               'textStyle')
        if (! all(names(label) %in% validLabelFeature))
            stop(paste("Only supports label features as below:\n",
                       validLabelFeature))
        lst$label <- label
    }

    defaultCheckpoint <- list(symbol="auto", symbolSize="auto", color="auto",
                              borderColor="auto", borderWidth="auto",
                              label=list(show=FALSE, textStyle=list(color="auto")))
    if (! identical(ifnull(checkpointStyle, defaultCheckpoint),
                    defaultCheckpoint)){
        validCheckpointFeature <- c('symbol', 'symbolSize', 'color', 'borderColor',
                                    'borderWidth', 'label')
        if (! all(names(checkpointStyle) %in% validCheckpointFeature))
            stop(paste("Only supports checkpointStyle features as below:\n",
                       validCheckpointFeature))
        lst$checkpointStyle <- checkpointStyle
    }

    defaultControl <- list(itemSize=15, itemGap=5, normal=list(color="#333"),
                           emphasis=list(color="#1e90ff"))
    if (! identical(ifnull(controlStyle, defaultControl), defaultControl)){
        validControlFeature <- c('itemSize', 'itemGap', 'normal', 'emphasis')
        if (! all(names(controlStyle) %in% validControlFeature))
            stop(paste("Only supports controlStyle features as below:\n",
                       validControlFeature))
        lst$controlStyle <- controlStyle
    }

    if (! ifnull(symbol, "emptyDiamond") == "emptyDiamond")
        lst$symbol <- symbol
    if (! ifnull(symbolSize, 4) == 4) lst$symbolSize <- symbolSize
    if (! ifnull(currentIndex, 0) == 0) lst$currentIndex <- currentIndex
    if (! is.null(data)) lst$data <- data
    chart$x$timeline <- lst

    return(chart %>% tuneGrid())
}

#' @export
#' @rdname setTimeline
setTL <- setTimeline

#' @export
#' @rdname setTimeline
set_timeline <- setTimeline

#' @export
#' @rdname setTimeline
set_tl <- setTimeline

#' Add \code{geoCoord} to An Echarts Object
#'
#' Add \code{geoCoord} object to echarts object. It is used for maps. \cr \cr
#' For maps with a timeline, \code{geoCoord} object is added to \code{options[[1]]$series[[1]]},
#' while for those without a timeline, it is added to \code{series[[1]]}.
#' @param chart chart \code{echarts} object generated by \code{\link{echart}} or
#' \code{\link{echartR}}
#' @param geoCoord Two mode: \cr
#' \describe{
#'  \item{list}{A typical geoCoord list should be: \code{list(place1=c(lng1, lat1), ...)}}
#'  \item{data.frame}{A data.frame of 3 columns: \code{place}, \code{longitude}, \code{latitude}}
#' }
#' @param mode 'add' or 'overide' \code{geoCoord} to the echarts object. Default 'add'.
#' You can use \code{\link{overideGeoCoord}} to overide existing \code{geoCoord}.
#'
#' @return A modified echarts object
#' @export
#'
#' @references \url{http://echarts.baidu.com/echarts2/doc/option.html#title~series-i(map).geoCoord}
#' @examples
#' \dontrun{
#'
#' }
addGeoCoord <- function(chart, geoCoord=NULL, mode=c('add', 'overide')){
    if (!inherits(chart, 'echarts'))
        stop('chart is not an Echarts object. ',
             'Check if you have missed a %>% in your pipe chain.')
    mode <- match.arg(mode)
    if (is.null(geoCoord)) return(chart)

    if (is.data.frame(geoCoord)){
        if (ncol(geoCoord) < 3) stop('geoCoord must contain place, lng, lat!')
        nameGeoCoord <- as.character(geoCoord[,1])
        geoCoord <- unname(apply(geoCoord, 1, function(row){
            as.list(as.numeric(row[2:3]))
        }))
        names(geoCoord) <- nameGeoCoord
        lstGeoCoord <- geoCoord
        if ('timeline' %in% names(chart$x)){
            if (chart$x$options[[1]]$series[[1]]$type == 'map')
                if (mode == 'add')
                    if ('geoCoord' %in% names(chart$x$options[[1]]$series[[1]]))
                        lstGeoCoord <- mergeList(
                            chart$x$options[[1]]$series[[1]]$geoCoord, geoCoord)
                chart$x$options[[1]]$series[[1]]$geoCoord <- lstGeoCoord
        }else{
            if (chart$x$series[[1]]$type == 'map')
                if (mode == 'add')
                    if ('geoCoord' %in% names(chart$x$series[[1]]))
                        lstGeoCoord <- mergeList(
                            chart$x$series[[1]]$geoCoord, geoCoord)
                chart$x$series[[1]]$geoCoord <- lstGeoCoord
        }
    }else if (is.list(geoCoord)){
        if (is.null(names(geoCoord))) stop('geoCoord list must be named with places!')
        if ('timeline' %in% names(chart$x)){
            if (chart$x$options[[1]]$series[[1]]$type == 'map')
                if (mode == 'add')
                    if ('geoCoord' %in% names(chart$x$options[[1]]$series[[1]]))
                        lstGeoCoord <- mergeList(
                            chart$x$options[[1]]$series[[1]]$geoCoord, geoCoord)
                    chart$x$options[[1]]$series[[1]]$geoCoord <- lstGeoCoord
        }else{
            if (chart$x$series[[1]]$type == 'map')
                if (mode == 'add')
                    if ('geoCoord' %in% names(chart$x$series[[1]]))
                        lstGeoCoord <- mergeList(
                            chart$x$series[[1]]$geoCoord, geoCoord)
                    chart$x$series[[1]]$geoCoord <- lstGeoCoord
        }
    }
    return(chart)
}

#' @export
#' @rdname addGeoCoord
addGC <- addGeoCoord

#' @export
#' @rdname addGeoCoord
add_gc <- addGeoCoord

#' @export
#' @rdname addGeoCoord
add_geocoord <- addGeoCoord

#' @export
#' @rdname addGeoCoord
overideGeoCoord <- function(chart, geoCoord=NULL){
    return(addGeoCoord(chart, geoCoord, mode='overide'))
}

#' @export
#' @rdname addGeoCoord
overideGC <- overideGeoCoord

#' Add \code{heatmap} to Echarts Object
#'
#' Add heatmap object to an Echarts object. (applicable for map)
#'
#' @param chart \code{echarts} object generated by \code{\link{echart}} or
#' \code{\link{echartR}}.
#' @param series Vector. Specify which series you want to insert heatmap. Could be
#' numeric (index of the series) or string (series name). If NULL, then apply to all.
#' Default NULL.
#' @param timeslots Vector. Specify which timeslots (t) you want to insert heatmap.
#' Could be numeric (index of the timeslot) or string (timeslot name).
#' If NULL, then apply to all. Default NULL. You could use \code{t} for short.
#' @param data The heatmap source data. Two modes:
#' \describe{
#'   \item{data.frame or matrix}{A data.frame or matrix of 3 columns: lng (-180 ~ 180),
#'   lat (-90 ~ 90) and heat value (0-1). If heat value is out of the range [0,1],
#'   then it will be normalized.}
#'   \item{list}{A list in the form: \code{list(c(<lng1>, <lat1>, <value1>),
#'   c(<lng2>, <lat2>, <value2>), ...)}}
#' }
#' @param gradientColors Color palette of heat visualization. Three mode:
#' \describe{
#'   \item{vector}{A vector of colors (hex, name, or rgba string)}
#'   \item{data.frame or matrix}{A data.frame or matrix of 2 columns: offset (0-1),
#'   and color (hex, name or rgba string). }
#'   \item{list}{A list in the form: \code{list(list(offset=0.2, color='red'),
#'   list(offset=0.5, color='green'), ...)}}
#' } \cr
#' Default c('blue', 'cyan', 'lime', 'yellow', 'orange', 'red').
#' @param blurSize Size of blur. Default 30.
#' @param minAlpha Minimal alpha value which any opacity value below this threshold
#' will be set \code{minAlpha} in order to prevent over-transparency. Default 0.05.
#' @param valueScale Numeric. Scale of value that all the heat value will be multiplied
#' by this value before plotting. Default 1.
#' @param opacity Numeric 0-1. Degree of opacity. Default 1.
#' @param mode 'add' or 'overide' \code{heatmap} to the echarts object. Default 'add'.
#' You can use \code{\link{overideHeatmap}} to overide existing \code{heatmap}.
#' @param ... Elipsis
#'
#' @return A modified echarts object
#' @export
#'
#' @references \url{http://echarts.baidu.com/echarts2/doc/option.html#title~series-i(map).heatmap}
#' @examples
#' \dontrun{
#'
#' }
addHeatmap <- function(chart, series=NULL, timeslots=NULL, data=NULL,
                       gradientColors=list(
                           'blue', 'cyan', 'limegreen', 'yellow', 'orange', 'red'),
                       blurSize=30, minAlpha=0.05,
                       valueScale=1, opacity=1, mode=c('add', 'overide'),
...){
    if (!inherits(chart, 'echarts'))
        stop('chart is not an Echarts object. ',
             'Check if you have missed a %>% in your pipe chain.')
    mode <- match.arg(mode)
    if ('t' %in% names(list(...))) timeslots <- list(...)[['t']]
    hasT <- 'timeline' %in% names(chart$x)

    if (is.null(data)) {
        return(chart)
    }else{
        if (is.data.frame(data) || is.matrix(data)){
            if (ncol(data) < 3)
                stop('data must be a matrix/data.frame ',
                     'with the columns lng, lat, heat (0-1) in order!')
            if (! all (data.table::between(data[,3], 0, 1)))
                data[,3] <- (data[,3]-min(data[,3], na.rm=TRUE))/
                    (max(data[,3], na.rm=TRUE)-min(data[,3], na.rm=TRUE))
            if (! all(data.table::between(data[,1], -180, 180)))
                stop('data[,1] (lng) must be between -180 and 180')
            if (! all(data.table::between(data[,2], -90, 90)))
                stop('data[,2] (lat) must be between -90 and 90')
            data <- asEchartData(unname(data[,1:3]))
        }else{
            stopifnot(is.list(data))
        }
    }

    # construct lstHeatmap
    lstHeatmap <- list(
        data=unname(data), gradientColors=gradientColors, blurSize=blurSize,
        minAlpha=minAlpha, valueScale=valueScale, opacity=opacity)

    # define series and t
    if (hasT){
        if (is.null(timeslots))
            timeslots <- seq_along(chart$x$options)
        else
            if (is.numeric(timeslots)){
                timeslots <- intersect(timeslots, seq_along(chart$x$options))
            }else{
                timeslots <- which(chart$x$timeline$data %in% timeslots)
            }
    }else{
        timeslots <- NULL
    }

    mapType <- getSeriesPart(chart, 'mapType')
    if (length(unique(mapType)) == 1){  # map mode is 'series'
        series <- 1
    }else{  # map mode is 'split'
        if (is.null(series)){
            series <- if (hasT)
                seq_len(max(sapply(chart$x$options, function(l) {
                    length(l$series)}))) else
                        seq_len(length(chart$x$series))
        }else{
            if (is.numeric(series)){
                series <- intersect(series, seq_along(getSeriesPart(
                    chart, 'category', fetch.all=TRUE)))
            }else{
                series <- which(getSeriesPart(chart, 'category', fetch.all=TRUE) %in%
                    intersect(getSeriesPart(chart, 'category', fetch.all=TRUE),
                              series))
            }
        }
    }

    # insert lstHeatmap
    if (is.null(timeslots)){  # no timeline
        for (s in ifnull(series, 1)){  # series is null, mono series
            lData <- chart$x$series[[s]]$heatmap$data
            chart$x$series[[s]]$heatmap <- lstHeatmap
            if (mode == 'add') if ('heatmap' %in% names(chart$x$series[[s]]))
                chart$x$series[[s]]$heatmap$data <- append(
                    lData, chart$x$series[[s]]$heatmap$data)
        }
    }else{  # with timeline
        for (t in timeslots){
            for (s in ifnull(series, 1)){  # series is null: mono series
                lData <- chart$x$options[[t]]$series[[s]]$heatmap$data
                chart$x$options[[t]]$series[[s]]$heatmap <- lstHeatmap
                if (mode == 'add')
                    if ('heatmap' %in% names(chart$x$options[[t]]$series[[s]]))
                        chart$x$options[[t]]$series[[s]]$heatmap$data <- append(
                            lData, chart$x$options[[t]]$series[[s]]$heatmap$data)
            }
        }
    }
    return(chart)
}

#' @export
#' @rdname addHeatmap
overideHeatmap <- function(chart, ...){
    return(addHeatmap(chart, mode='overide', ...))
}

#' @export
#' @rdname addHeatmap
addHM <- addHeatmap

#' @export
#' @rdname addHeatmap
add_heatmap <- addHeatmap

#' @export
#' @rdname addHeatmap
add_hm <- addHeatmap

#' @export
#' @rdname addHeatmap
overideHM <- overideHeatmap

#' Add \code{nameMap} to Echarts Object
#'
#' For map charts, you can add \code{nameMap} to translate the place names from one to another.
#' The \code{nameMap} object will be inserted to the first series of the echarts object.
#'
#' @param chart \code{echarts} object generated by \code{\link{echart}} or
#' \code{\link{echartR}}.
#' @param nameMap Two modes: \cr
#' \describe{
#'  \item{data.frame/matrix}{A data.frame or matrix comprising of 2 columns: \code{nameToTranslate &
#'  nameTranslatedTo}. E.g., You can load a preinstalled Chinese-English geographic
#'  dictionary using \code{recharts:::geoNameMap}.}
#'  \item{list}{The nameMap in list should follow the structure: \code{list(list(
#' `United States of America`='USA'), list('United Kingdom'='GB'), ...)}}
#' }
#' @param mode 'add' or 'overide' \code{nameMap} to the echarts object. Default 'add'.
#' You can use \code{\link{overideNameMap}} to overide existing \code{nameMap}.
#'
#' @return A modified echarts object
#' @export
#'
#' @seealso \code{data(geoNameMap)}
#' @references \url{http://echarts.baidu.com/echarts2/doc/option.html#title~series-i(map).nameMap}
#' @examples
#' \dontrun{
#' }
addNameMap <- function(chart, nameMap, mode=c('add', 'overide')){
    if (!inherits(chart, 'echarts'))
        stop('chart is not an Echarts object. ',
             'Check if you have missed a %>% in your pipe chain.')
    mode <- match.arg(mode)
    if (is.data.frame(nameMap) || is.matrix(nameMap)){
        stopifnot(ncol(nameMap) > 1)
        if (! (is.character(nameMap[,1]) && is.character(nameMap[,2])))
            stop('nameMap[,1] and nameMap[,2] must both be characters.')
        lstNameMap <- asEchartData(nameMap[,2])
        names(lstNameMap) <- nameMap[,1]
    }else if (is.list(nameMap)){
        stopifnot(length(unlist(nameMap)) == length(nameMap))
        if (is.null(names(nameMap)))
            stop('list nameMap must be named with the names you want to translate!')
        lstNameMap <- nameMap
    }
    hasT <- 'timeline' %in% names(chart$x)
    if(hasT){
        if (mode == 'add')
            if ('nameMap' %in% names(chart$x$options[[1]]$series[[1]]))
                lstNameMap <- mergeList(
                    chart$x$options[[1]]$series[[1]]$nameMap, lstNameMap)
        chart$x$options[[1]]$series[[1]]$nameMap <- lstNameMap
    }else{
        if (mode == 'add')
            if ('nameMap' %in% names(chart$x$series[[1]]))
                lstNameMap <- mergeList(
                    chart$x$series[[1]]$nameMap, lstNameMap)
        chart$x$series[[1]]$nameMap <- lstNameMap
    }
    return(chart)
}

#' @export
#' @rdname addNameMap
overideNameMap <- function(chart, ...){
    return(addNameMap(chart, mode='overide', ...))
}

#' @export
#' @rdname addNameMap
addNM <- addNameMap

#' @export
#' @rdname addNameMap
add_namemap <- addNameMap

#' @export
#' @rdname addNameMap
add_nm <- addNameMap

#' @export
#' @rdname addNameMap
overideNM <- overideNameMap

#' Add \code{markLine} to An Echarts Object
#'
#' @param chart \code{echarts} object generated by \code{\link{echart}} or \code{\link{echartR}}
#' @param series Numeric (series index) or character (series name), numeric preferred.
#' If set NULL, then apply the \code{markPoint} to all the series.
#' @param timeslots Numeric (timeslot index) or character (timeslot name), numeric is preferred.
#' If set NULL, then apply the \code{markPoint} to all the timeslots. You can use \code{t}
#' for short.
#' @param data Data.frame, the data of the \code{markLine}s. It must contain the
#' following columns: \code{\strong{name1}}, \code{name2, value | type} and/or
#' \code{x1 | xAxis1, y1 | yAxis1, x2 | xAxis2, y2 | yAxis2} and/or \code{series}. \cr \cr
#' When series is given, it must be cooresponding to the \code{series} argument.
#' \describe{
#'  \item{generic}{generic form is columns \code{[name1, name2, value, x1, y1, x2, y2]}.
#'  The minimum form is \code{[name1, x1, y1, x2, y2]}}
#'  \item{scatter, line, bar}{\code{[name1, name2, value, x1, y1, x2, y2]} or
#'    \code{[name1, type]} (type can only be 'max', 'min' or 'average') or
#'    \code{[name1, name2, value, xAxis1, yAxis1, xAxis2, yAxis2]}}
#'  \item{k, eventRiver}{\code{[name1, name2, value, x1, y1, x2, y2]} or
#'    \code{[name1, name2, value, xAxis1, yAxis1, xAxis2, yAxis2]}}
#'  \item{map}{\code{[name1, name2, value]}. \strong{You need to pass in \code{
#'  [name1, lng1, lat1], [name2, lng2, lat2]} using \code{\link{addGeoCoord}}} separately.}
#' }
#' @param clickable Logical, if the graphs are clickable. Default TRUE.
#' @param symbol Symbol vector of the markLines, refer to \code{recharts:::validSymbols}.
#' Default \code{c('circle', 'arrow')}.
#' @param symbolSize Numeric vector or \code{JS} function defining the size of the
#' the beginning symbol and the end symbol of the marklines. Default \code{c(2, 4)}.
#' @param symbolRotate Numeric -180 ~ 180. Default NULL.
#' @param large Logical if large effect is on. Default FALSE.
#' @param effect List. effect configurator of markLines. Default NULL, which is
#' \code{list(show=FALSE, loop=TRUE, period=15, scaleSize=2, color=NULL,
#' shadowColor=NULL, shadowBlur=0)}
#' @param itemStyle List. It is a list with the structure \code{list(normal=list(...),
#' emphasis=list(...))}. Default NULL.
#' @param mode 'add' or 'overide' the data part of \code{markLine} to the echarts
#' object. Default 'add'.
#' You can use \code{\link{overideMarkLine}} to overide the data of the existing
#' \code{markLine}.
#' @param ... Elipsis
#' @export
#'
#' @references \url{http://echarts.baidu.com/echarts2/doc/option.html#tooltip-line1~series-i(line).markLine}
#' @examples
#' \dontrun{
#' g <- echartr(iris, Sepal.Width, Petal.Width, Species)
#' g %>% addML(c('setosa', 'versicolor'), data=data.frame(name1=rep('mean', 2),
#'            type=rep('average', 2), series=c('setosa', 'versicolor')))
#' }
#'
addMarkLine <- function(
    chart, series=NULL, timeslots=NULL, data=NULL, clickable=TRUE,
    symbol=c('circle', 'arrow'), symbolSize=c(2,4), symbolRotate=NULL,
    large=FALSE, smooth=FALSE, smoothness=0.2, precision=2,
    bundling=list(enable=FALSE, maxTurningAngle=45),
    effect=list(show=FALSE), itemStyle=NULL, mode=c('add', 'overide'),
...){
    if (!inherits(chart, 'echarts'))
        stop('chart is not an Echarts object. ',
             'Check if you have missed a %>% in your pipe chain.')
    if ('t' %in% names(list(...))) timeslots <- list(...)[['t']]
    hasT <- 'timeline' %in% names(chart$x)

    # data validation
    if (is.null(data)) {
        return(chart)
    }else{
        if (is.data.frame(data)){
            names(data) <- tolower(names(data))
            data <- data[, intersect(names(data), c(
                'name1', 'name2', 'value', 'type', 'x1', 'y1', 'xaxis1', 'yaxis1',
                'x2', 'y2', 'xaxis2', 'yaxis2', 'series'))]

            if (! 'name1' %in% names(data))
                stop('`data` must be a data.frame with the columns: ',
                     'name1 and/or name2, and/or value|type, and/or x1|xAxis1, ',
                     'y1|yAxis1, x2|xAxis2, y2|yAxis2 and/or series!')
            stopifnot(all(tolower(data$type) %in% c(NA, 'min', 'max', 'average')))
            data <- data[!is.na(data$name1),]
            names(data) <- tolower(names(data))
        }else{
            stop('`data` must be a data.frame.')
        }
    }

    # other params
    if (!missing(symbol)) {
        intersectSymbols <- symbol[tolower(symbol) %in% tolower(validSymbols)]
        iSymbol <- sapply(intersectSymbols, function(symbol) {
            return(which(tolower(validSymbols) == tolower(symbol)))
        })
        symbol <- as.vector(validSymbols[unlist(iSymbol)])
    }

    # define t
    if (hasT){
        if (is.null(timeslots))
            timeslots <- seq_along(chart$x$options)
        else
            if (is.numeric(timeslots)){
                timeslots <- intersect(timeslots, seq_along(chart$x$options))
            }else{
                timeslots <- which(chart$x$timeline$data %in% timeslots)
            }
    }else{
        timeslots <- NULL
    }

    # define series
    lstAnalyzeSeries <- analyzeSeries(chart, series)
    series <- lstAnalyzeSeries$numSeries
    lvlseries <- lstAnalyzeSeries$strSeries
    allSeries <- lstAnalyzeSeries$allnumSeries
    lvlSeries <- lstAnalyzeSeries$allStrSeries
    newSeries <- lstAnalyzeSeries$strNewSeries

    # construct base lstMarkline
    lstMark <- list(
        data=list(), clickable=clickable, symbol=symbol, smooth=smooth,
        symbolSize=symbolSize, large=large, effect=effect, smoothness=smoothness,
        precision=precision, bundling=bundling)
    if (!is.null(symbolRotate)) if (symbolRotate <= 180 && symbolRotate >= -180)
        lstMark$symbolRotate <- symbolRotate
    if (!is.null(itemStyle))
        if (is.list(itemStyle)){
            lstMark$itemStyle <- itemStyle[names(itemStyle) %in% c('normal', 'emphasis')]
            if (! all(names(itemStyle) %in% c('normal', 'emphasis')))
                warning('itemStyle must be comprised of list(normal) and/or list(emphasis)')
        }

    .mkMLbyRow <- function(row, type){
        o <- list(list(name=unname(row['name1'])), list())
        if (!is.null(row['name2'])) o[[2]]$name <- ifna(unname(row['name2']), '')
        if (!is.null(row['value'])) o[[1]]$value <- ifna(unname(row['value']), '')
        if (!is.null(row['x1'])) o[[1]]$x <- ifna(as.numeric(row['x1']), '-')
        if (!is.null(row['y1'])) o[[1]]$y <- ifna(as.numeric(row['y1']), '-')
        if (!is.null(row['x2'])) o[[2]]$x <- ifna(as.numeric(row['x2']), '-')
        if (!is.null(row['y2'])) o[[2]]$y <- ifna(as.numeric(row['y2']), '-')

        if (type %in% c('scatter', 'line', 'bar', 'k', 'eventRiver')){
            if (!is.null(row['xaxis1']))
                if (!is.na(row['xaxis1'])){
                    o[[1]]$xAxis <- ifna(as.numeric(row['xaxis1']),
                                         unname(row['xaxis1']))
                    o[[1]]$x <- NULL
                }
            if (!is.null(row['yaxis1']))
                if (!is.na(row['yaxis1'])){
                    o[[1]]$yAxis <- ifna(as.numeric(row['yaxis1']),
                                          unname(row['yaxis1']))
                    o[[1]]$y <- NULL
                }

            if (!is.null(row['xaxis2']))
                if (!is.na(row['xaxis2'])){
                    o[[2]]$xAxis <- ifna(as.numeric(row['xaxis2']),
                                         unname(row['xaxis2']))
                    o[[2]]$x <- NULL
                }
            if (!is.null(row['yaxis2']))
                if (!is.na(row['yaxis2'])){
                    o[[2]]$yAxis <- ifna(as.numeric(row['yaxis2']),
                                         unname(row['yaxis2']))
                    o[[2]]$y <- NULL
                }

            if (type %in% c('scatter', 'line', 'bar'))
                if (!is.null(row['type'])) if (!is.na(row['type'])){
                    o <- list(
                        name=unname(ifna(row['name1'], ifna(row['name2'], ''))),
                        type=unname(row['type']))
                }

        }else if (type %in% c('map')){
            o <- list(list(name=unname(row['name1'])),
                      list(name=unname(row['name2'])))
            if (!is.na(row['value'])) o[[1]]$value <- as.numeric(row['value'])
        }else{

        }
        return(o)
    }

    # insert lstMarkline
    lstML <- lstMark
    if (is.null(timeslots)){  # no timeline
        if (length(series) > 0)
            for (s in unlist(series)){  # series is null, mono series
                if ('series' %in% names(data)){
                    dt <- data[data$series == lvlseries[which(series==s)],]
                }else dt <- data
                lstML$data <- unname(apply(dt, 1, .mkMLbyRow,
                                           type=chart$x$series[[s]]$type))
                if ('markLine' %in% names(chart$x$series[[s]]))
                    lstML$data <- append(
                        chart$x$series[[s]]$markLine$data, lstML$data)
                chart$x$series[[s]]$markLine <- lstML
            }

        if (length(newSeries) > 0){
            for (ns in seq_along(newSeries)){
                chart$x$series <- append(chart$x$series, list(list(
                    name=newSeries[ns], type=chart$x$series[[1]]$type,
                    data=list(list('-')))))
                idxNew <- length(chart$x$series)
                dt <- if ('series' %in% names(data))
                    data[data$series==newSeries[ns],] else data
                lstML$data <- unname(apply(dt, 1, .mkMLbyRow,
                                           type=chart$x$series[[ns]]$type))
                chart$x$series[[idxNew]]$markLine <- lstML
                chart$x$legend$data <- append(
                    chart$x$legend$data, newSeries[ns]
                )
                if (chart$x$series[[1]]$type == 'map')
                    chart$x$series[[idxNew]]$mapType <- chart$x$series[[1]]$mapType
            }
        }

    }else{  # with timeline
        for (t in timeslots){
            if (length(series) >0)
                for (s in series[[t]]){
                    # series is null, mono series
                    dt <- if ('series' %in% names(data))
                        data[data$series==allSeries[[t]][which(series==s)],] else data
                    lstML$data <- unname(apply(
                        dt, 1, .mkMLbyRow,
                        type=chart$x$options[[t]]$series[[s]]$type))
                    if ('markLine' %in% names(chart$x$options[[t]]$series[[s]]))
                        lstML$data <- append(
                            chart$x$options[[t]]$series[[s]]$markLine$data,
                            lstML$data)
                    chart$x$options[[t]]$series[[s]]$markLine <- lstML
                }

            if (length(newSeries) > 0){
                for (ns in seq_along(newSeries)){
                    chart$x$options[[t]]$series <-
                        append(chart$x$options[[t]]$series, list(list(
                            name=newSeries[ns], data=list(list('-')),
                            type=chart$x$options[[t]]$series[[1]]$type)))
                    dt <- if ('series' %in% names(data))
                        data[data$series==newSeries[ns],] else data
                    lstML$data <- unname(apply(
                        dt, 1, .mkMLbyRow,
                        type=chart$x$options[[t]]$series[[ns]]$type))
                    idxNew <- length(chart$x$options[[t]]$series)
                    chart$x$options[[t]]$series[[idxNew]]$markLine <- lstML
                    chart$x$options[[1]]$legend$data <- append(
                        chart$x$options[[1]]$legend$data, newSeries[ns]
                    )
                    if (chart$x$options[[t]]$series[[1]]$type == 'map')
                        chart$x$options[[t]]$series[[idxNew]]$mapType <-
                            chart$x$options[[t]]$series[[1]]$mapType
                }
            }
        }
    }
    return(chart)
}

#' @export
#' @rdname addMarkLine
addML <- addMarkLine

#' @export
#' @rdname addMarkLine
addMarkline <- addMarkLine

#' @export
#' @rdname addMarkLine
add_markline <-addMarkLine

#' @export
#' @rdname addMarkLine
add_ml <- addMarkLine

#' @export
#' @rdname addMarkLine
overideMarkLine <- function(chart, ...){
    return(addMarkLine(chart, mode='overide', ...))
}

#' @export
#' @rdname addMarkLine
overideMarkline <- overideMarkLine

#' @export
#' @rdname addMarkLine
overideML <-overideMarkLine


#' Add \code{markPoint} to An Echarts Object
#'
#' @param chart \code{echarts} object generated by \code{\link{echart}} or \code{\link{echartR}}
#' @param series Numeric (series index) or character (series name), numeric preferred.
#' If set NULL, then apply the \code{markPoint} to all the series.
#' @param timeslots Numeric (timeslot index) or character (timeslot name), numeric preferred.
#' If set NULL, then apply the \code{markPoint} to all the timeslots. You can use \code{t}
#' for short.
#' @param data Data.frame, the data of the \code{markPoint}s. It must contain the
#' following columns: \code{\strong{name}}, and/or \code{value | type} and/or
#' \code{x | xAxis, y | yAxis} and/or \code{series}. \cr \cr
#' When series is given, it must be corresponding to the \code{series} argument.
#' \describe{
#'  \item{generic}{generic form is columns \code{[name, x, y]} or \code{[name, value,
#'    x, y]}}
#'  \item{scatter, line, bar}{\code{[name, value, x, y]} or \code{[name, type]}
#'  (type can only be 'max' or 'min') or \code{[name, value, xAxis, yAxis]}}
#'  \item{k, eventRiver}{\code{[name, value, x, y]} or \code{[name, value, xAxis, yAxis]}}
#'  \item{map}{\code{[name, value]}. \strong{You need to pass in \code{[name, lng, lat]}
#'  using \code{\link{addGeoCoord}}} separately.}
#' }
#' Note that markLine dataset is compatible with \code{addMarkPoint}.
#' \itemize{
#'  \item \code{x1, x2} are treated as \code{x} (keep the first detected one only)
#'  \item \code{y1, y2} are treated as \code{y} (keep the first detected one only)
#'  \item \code{xAxis1, xAxis2} are treated as \code{xAxis} (keep the first detected
#'   one only)
#'  \item \code{yAxis1, yAxis2} are treated as \code{yAxis} (keep the first detected
#'   one only
#' }
#' @param clickable Logical, if the points are clickable. Default TRUE.
#' @param symbol Symbol of the markpoints, refer to \code{recharts:::validSymbols}.
#' Default 'pin'.
#' @param symbolSize Numeric or vector \code{c(height, width)} or JS function. Default 10.
#' @param symbolRotate Numeric -180 ~ 180. Default NULL.
#' @param large Logical if large effect is on. Default FALSE.
#' @param effect List. effect configurator of markPoints. Default NULL, which is
#' \code{list(show=FALSE, type='scale', loop=TRUE, period=15, scaleSize=2,
#' bounceDistance=10, color=NULL, shadowColor=NULL, shadowBlur=0)}
#' @param itemStyle List. It is a list with the structure \code{list(normal=list(...),
#' emphasis=list(...))}. Default NULL.
#' @param mode 'add' or 'overide' the data part of \code{markPoint} to the echarts
#' object. Default 'add'.
#' You can use \code{\link{overideMarkPoint}} to overide the data of the existing
#' \code{markPoint}.
#' @param ... Elipsis
#'
#' @export
#' @references \url{http://echarts.baidu.com/echarts2/doc/option.html#tooltip-line1~series-i(line).markPoint}
#' @seealso \code{\link{addMarkLine}}  \code{\link{addMarkline}}
#' @examples
#' \dontrun{
#' g <- echartr(iris, Sepal.Width, Petal.Width, Species)
#' g %>% addMP(c('setosa', 'versicolor'), data=data.frame(name=c('max', 'min'),
#'            type=c('max', 'min'), series=c('setosa', 'versicolor')))
#' }
#'
addMarkPoint <- function(
    chart, series=NULL, timeslots=NULL, data=NULL, clickable=TRUE, symbol='pin',
    symbolSize=10, symbolRotate=NULL, large=FALSE, effect=list(show=FALSE),
    itemStyle=NULL, ...){
    if (!inherits(chart, 'echarts'))
        stop('chart is not an Echarts object. ',
             'Check if you have missed a %>% in your pipe chain.')
    if ('t' %in% names(list(...))) timeslots <- list(...)[['t']]
    hasT <- 'timeline' %in% names(chart$x)

    # data validation
    if (is.null(data)) {
        return(chart)
    }else{
        if (is.data.frame(data)){
            names(data) <- tolower(names(data))
            if (! 'name' %in% names(data))
                names(data) <- sub('name\\d*', 'name', names(data))
            if (! 'x' %in% names(data))
                names(data) <- sub('x\\d*', 'x', names(data))
            if (! 'y' %in% names(data))
                names(data) <- sub('y\\d*', 'y', names(data))
            if (! 'xaxis' %in% names(data))
                names(data) <- sub('xaxis\\d*', 'xaxis', names(data))
            if (! 'yaxis' %in% names(data))
                names(data) <- sub('yaxis\\d*', 'yaxis', names(data))

            data <- data[, intersect(names(data), c(
                'name', 'value', 'type', 'x', 'y', 'xaxis', 'yaxis', 'series'))]
            if (! 'name' %in% names(data))
                stop('`data` must be a data.frame with the columns: ',
                     'name, and/or value|type, and/or x|xAxis, y|yAxis and/or series!')
            stopifnot(all(tolower(data$type) %in% c(NA, 'min', 'max')))
            data <- data[!is.na(data$name),]
            names(data) <- tolower(names(data))
        }else{
            stop('`data` must be a data.frame.')
        }
    }

    # other params
    if (!missing(symbol)) {
        intersectSymbols <- symbol[tolower(symbol) %in% tolower(validSymbols)]
        iSymbol <- sapply(intersectSymbols, function(symbol) {
            return(which(tolower(validSymbols) == tolower(symbol)))
        })
        symbol <- as.vector(validSymbols[unlist(iSymbol)])
    }

    # define t
    if (hasT){
        if (is.null(timeslots))
            timeslots <- seq_along(chart$x$options)
        else
            if (is.numeric(timeslots)){
                timeslots <- intersect(timeslots, seq_along(chart$x$options))
            }else{
                timeslots <- which(chart$x$timeline$data %in% timeslots)
            }
    }else{
        timeslots <- NULL
    }

    # define series
    lstAnalyzeSeries <- analyzeSeries(chart, series)
    series <- lstAnalyzeSeries$numSeries
    lvlseries <- lstAnalyzeSeries$strSeries
    allSeries <- lstAnalyzeSeries$allnumSeries
    lvlSeries <- lstAnalyzeSeries$allStrSeries
    newSeries <- lstAnalyzeSeries$strNewSeries

    # construct base lstMarkpoint
    lstMark <- list(
        data=list(), clickable=clickable, symbol=symbol,
        symbolSize=symbolSize, large=large, effect=effect)
    if (!is.null(symbolRotate)) if (symbolRotate <= 180 && symbolRotate >= -180)
        lstMark$symbolRotate <- symbolRotate
    if (!is.null(itemStyle))
        if (is.list(itemStyle)){
            lstMark$itemStyle <- itemStyle[names(itemStyle) %in% c('normal', 'emphasis')]
            if (!all(names(itemStyle) %in% c('normal', 'emphasis')))
                warning('itemStyle must be comprised of list(normal) and/or list(emphasis)')
        }
    .mkMPbyRow <- function(row, type){
        o <- list()
        if (type %in% c('scatter', 'line', 'bar', 'k', 'eventRiver')){
            if (type %in% c('scatter', 'line', 'bar')){
                if (!is.na(row['type'])) o <- as.list(row[c('name', 'type')])
            }
            if (!is.null(row['xaxis'])){
                if (!is.na(row['xaxis']))
                    o$xAxis <- ifna(as.numeric(row['xaxis']), unname(row['xaxis']))
            }else if (!is.null(row['x'])){
                if (!is.na(row['x'])) o$x <- ifna(as.numeric(row['x']), unname(row['x']))
            }
            if (!is.null(row['yaxis'])){
                if (!is.na(row['yaxis']))
                    o$yAxis <- ifna(as.numeric(row['yaxis']), unname(row['yaxis']))
            }else if (!is.null(row['y'])){
                if (!is.na(row['y'])) o$y <- ifna(as.numeric(row['y']), unname(row['y']))
            }
        }else if (type %in% c('map')){
            o <- as.list(row[c('name', 'value')])
            o$value <- ifna(as.numeric(o$value), '-')
        }else{
            o <- list(name=unname(row['name']),
                      value=ifna(as.numeric(row['value']), '-'),
                      x=ifna(as.numeric(row['x']), '-'),
                      y=ifna(as.numeric(row['y']), '-'))
        }
        return(o)
    }

    # insert lstMarkpoint
    lstMP <- lstMark
    if (is.null(timeslots)){  # no timeline
        if (length(series) >0)
            for (s in unlist(series)){  # series is null, mono series
                if ('series' %in% names(data)){
                    dt <- data[data$series == lvlseries[which(series==s)],]
                }else dt <- data
                lstMP$data <- unname(apply(dt, 1, .mkMPbyRow,
                                           type=chart$x$series[[s]]$type))
                if ('markPoint' %in% names(chart$x$series[[s]]))
                    lstMP$data <- append(
                        chart$x$series[[s]]$markPoint$data, lstMP$data)
                chart$x$series[[s]]$markPoint <- lstMP
            }

        if (length(newSeries) > 0){
            for (ns in seq_along(newSeries)){
                chart$x$series <- append(chart$x$series, list(list(
                    name=newSeries[ns], type=chart$x$series[[1]]$type,
                    data=list(list('-')))))
                dt <- if ('series' %in% names(data))
                    data[data$series==newSeries[ns],] else data
                lstMP$data <- unname(apply(dt, 1, .mkMPbyRow,
                                           type=chart$x$series[[ns]]$type))
                idxNew <- length(chart$x$series)
                chart$x$series[[idxNew]]$markPoint <- lstMP
                chart$x$legend$data <- append(
                    chart$x$legend$data, newSeries[ns])
                if (chart$x$series[[1]]$type == 'map')
                    chart$x$series[[idxNew]]$mapType <- chart$x$series[[1]]$mapType
            }
        }
    }else{  # with timeline
        for (t in timeslots){
            if (length(series) >0)
                for (s in series[[t]]){  # series is null, mono series
                    dt <- if ('series' %in% names(data))
                        data[data$series==allSeries[[t]][which(series==s)],] else data
                    lstMP$data <- unname(apply(
                        dt, 1, .mkMPbyRow,
                        type=chart$x$options[[t]]$series[[s]]$type))
                    if ('markPoint' %in% names(chart$x$options[[t]]$series[[s]]))
                        lstMP$data <- append(
                            chart$x$options[[t]]$series[[s]]$markPoint$data,
                            lstMP$data)
                    chart$x$options[[t]]$series[[s]]$markPoint<- lstMP
                }

            if (length(newSeries) > 0){
                for (ns in seq_along(newSeries)){
                    chart$x$options[[t]]$series <- append(
                        chart$x$options[[t]]$series, list(list(
                            name=newSeries[ns], data=list(list('-')),
                            type=chart$x$options[[t]]$series[[1]]$type)))
                    dt <- if ('series' %in% names(data))
                        data[data$series==newSeries[ns],] else data
                    lstMP$data <- unname(apply(
                        dt, 1, .mkMPbyRow,
                        type=chart$x$options[[t]]$series[[ns]]$type))
                    idxNew <- length(chart$x$options[[t]]$series)
                    chart$x$options[[t]]$series[[idxNew]]$markPoint <- lstMP
                    chart$x$options[[1]]$legend$data <- append(
                        chart$x$options[[1]]$legend$data, newSeries[ns]
                    )
                    if (chart$x$options[[t]]$series[[1]]$type == 'map')
                        chart$x$options[[t]]$series[[idxNew]]$mapType <-
                            chart$x$options[[t]]$series[[1]]$mapType
                }
            }
        }
    }
    return(chart)
}

#' @export
#' @rdname addMarkPoint
addMP <- addMarkPoint

#' @export
#' @rdname addMarkPoint
add_markpoint <- addMarkPoint

#' @export
#' @rdname addMarkPoint
add_mp <- addMarkPoint

#' @export
#' @rdname addMarkPoint
addMarkpoint <- addMarkPoint

#' @export
#' @rdname addMarkPoint
overideMarkPoint <- function(chart, ...){
    return(addMarkPoint(chart, mode='overide', ...))
}

#' @export
#' @rdname addMarkPoint
overideMarkpoint <- overideMarkPoint

#' @export
#' @rdname addMarkPoint
overideMP <- overideMarkPoint

#' @export
#' @rdname addMarkPoint
overide_mp <- overideMarkPoint

#' @export
#' @rdname addMarkPoint
overide_markpoint <- overideMarkPoint

#' Set `series` Element by Element
#'
#' The basic framework of making Echarts object is Step 1 \code{echart} => Step 2
#' \code{set[Widgets]} => Step 3 \code{set[Aesthetics]}. Step 1 makes the structure
#' of the Echarts object, step 2 creates/modifies its widgets, and step 3 modifies
#' the params of the widgets. But what if you want to modify the details inside
#' \code{series} list? -- You may want \code{setSeries}.\cr \cr
#' \code{setSeries} is a bit low-level, compared to other \code{set[Widgets]} functions.
#' It directly pass the param list on to an Echarts object and may lead to unexpected
#' results. So you are suggested to get more familiar with Echarts structure before
#' you decide to \code{setSeries}.
#'
#' @param chart chart \code{echarts} object generated by \code{\link{echart}} or
#' \code{\link{echartR}}
#' @param timeslots vector of timeslots, either name or index of timeline variable
#' \code{t}. But you cannot use \code{t} as \code{timeslots} for short in \code{setSeries}.
#' @param series series, could be the name or the index of the series. When the string
#' is not in the existing series list, it will be treated as new
#' @param ... The params to pass to the echarts object for modification. \cr
#' The generic params include:
#' \tabular{ll}{
#'  \code{type} \tab chart type. 'line', 'bar', 'scatter', 'k', 'pie', 'radar',
#'   'chord', 'force', 'map'... If NULL, the series will not display. \cr
#'  \code{name} \tab name of the series. \cr
#'  \code{tooltip} \tab \code{\link{setTooltip}} \cr
#'  \code{data} \tab the \code{data} part of \code{\link{echartR}} \cr
#'  \code{itemStyle} \tab \code{\link{itemStyle}} \cr
#'  \code{markPoint} \tab \code{\link{addMarkPoint}} \cr
#'  \code{markLine} \tab \code{\link{addMarkLine}} \cr
#'  \code{clickable} \tab whether the data graph is clickable, default TRUE. \cr
#'  \code{z} \tab level-2 layer control, default 2. \cr
#'  \code{zlevel} \tab level-1 layer control, default 0.
#' } \cr
#' The type-specific params include:
#' \describe{
#'  \item{line}{
#'    \tabular{ll}{
#'      \code{stack} \tab TRUE|FALSE \cr
#'      \code{xAxisIndex} \tab 0|1, default 0 (main axis) \cr
#'      \code{yAxisIndex} \tab 0|1, default 0 (main axis) \cr
#'      \code{symbol} \tab \code{\link{setSymbols}} \cr
#'      \code{symbolSize} \tab numeric or \code{\link{JS}} function \cr
#'      \code{symbolRotate} \tab -180 ~ 180 \cr
#'      \code{showAllSymbol} \tab TRUE|FALSE, default FALSE\cr
#'      \code{smooth} \tab TRUE|FALSE, default FALSE \cr
#'      \code{dataFilter} \tab only for line charts. 'nearest'|'min'|'max'|'average', default 'nearest'. \cr
#'      \code{legendHoverLink} \tab TRUE|FALSE, default TRUE.
#'    }}
#'  \item{bar}{
#'    \tabular{ll}{
#'      \code{stack} \tab TRUE|FALSE \cr
#'      \code{xAxisIndex} \tab 0|1, default 0 (main axis) \cr
#'      \code{yAxisIndex} \tab 0|1, default 0 (main axis) \cr
#'      \code{barGap} \tab percent or numeric, default '30\%'. \cr
#'      \code{barCategoryGap} \tab percent or numeric, default '20\%'. \cr
#'      \code{barMinHeight} \tab numeric, default 0. \cr
#'      \code{barWidth} \tab numeric, default automatic. \cr
#'      \code{barMaxWidth} \tab numeric, default automatic. \cr
#'      \code{legendHoverLink} \tab TRUE|FALSE, default TRUE.
#'      }}
#'  \item{scatter}{
#'    \tabular{ll}{
#'      \code{xAxisIndex} \tab 0|1, default 0 (main axis) \cr
#'      \code{yAxisIndex} \tab 0|1, default 0 (main axis) \cr
#'      \code{symbol} \tab \code{\link{setSymbols}} \cr
#'      \code{symbolSize} \tab numeric or \code{\link{JS}} function \cr
#'      \code{symbolRotate} \tab -180 ~ 180 \cr
#'      \code{large} \tab TRUE|FALSE, default FALSE. \cr
#'      \code{largeThreshold} \tab numeric, default 2000. \cr
#'      \code{legendHoverLink} \tab TRUE|FALSE, default TRUE.
#'      }}
#'  \item{pie}{
#'    \tabular{ll}{
#'      \code{legendHoverLink} \tab TRUE|FALSE, default TRUE. \cr
#'      \code{center} \tab percent or numeric, default c('50\%', '50\%') \cr
#'      \code{radius} \tab percent or numeric, default c(0, '75\%') \cr
#'      \code{startAngle} \tab numeric -360 ~ 360, default 90 for pie, 225 for gauge. \cr
#'      \code{minAngle} \tab numeric, default 0. \cr
#'      \code{clockWise} \tab TRUE|FALSE, default TRUE. \cr
#'      \code{roseType} \tab 'radius'|'area'. \cr
#'      \code{selectedOffset} \tab numeric, default 10. \cr
#'      \code{selectedMode} \tab NULL|'single'|'multiple', default NULL.
#'      }}
#'  \item{radar}{
#'    \tabular{ll}{
#'      \code{symbol} \tab \code{\link{setSymbols}} \cr
#'      \code{symbolSize} \tab numeric or \code{\link{JS}} function \cr
#'      \code{symbolRotate} \tab -180 ~ 180 \cr
#'      \code{legendHoverLink} \tab TRUE|FALSE, default TRUE. \cr
#'      \code{polarIndex} \tab numeric. Index of the polar system to host the series.
#'      }}
#'  \item{chord}{
#'    \tabular{ll}{
#'      \code{symbol} \tab \code{\link{setSymbols}} \cr
#'      \code{symbolSize} \tab numeric or \code{\link{JS}} function \cr
#'      \code{clockWise} \tab TRUE|FASLE, default FALSE. \cr
#'      \code{categories} \tab character vector \cr
#'      \code{links} \tab list of links \cr
#'      \code{matrix} \tab matrix data \cr
#'      \code{minRadius} \tab numeric, default 10. \cr
#'      \code{maxRadius} \tab numeric, default 20. \cr
#'      \code{ribbonType} \tab TRUE|FALSE, default TRUE. \cr
#'      \code{showScale} \tab TRUE|FALSE, default FALSE. \cr
#'      \code{showScaleText} \tab TRUE|FALSE, default FALSE \cr
#'      \code{padding} \tab numeric, default 2. \cr
#'      \code{sort} \tab 'none'|'ascending'|'descending', default 'none'. \cr
#'      \code{sortSub} \tab 'none'|'ascending'|'descending', default 'none'. \cr
#'      \code{nodes} \tab nodes list
#'      }}
#'  \item{force}{
#'    \tabular{ll}{
#'      \code{symbol} \tab \code{\link{setSymbols}} \cr
#'      \code{symbolSize} \tab numeric or \code{\link{JS}} function. \cr
#'      \code{large} \tab TRUE|FALSE, default FALSE. \cr
#'      \code{center} \tab percent or numeric, default c('50\%', '50\%'). \cr
#'      \code{roam} \tab TRUE|FALSE|'scale'|'move', default FALSE. \cr
#'      \code{categories} \tab character vector \cr
#'      \code{links} \tab links list \cr
#'      \code{matrix} \tab data matrix \cr
#'      \code{size} \tab percent or numeric, default '100\%' \cr
#'      \code{minRadius} \tab numeric, default 10. \cr
#'      \code{maxRadius} \tab numeric, default 20. \cr
#'      \code{linkSymbol} \tab vector of symbol characters, default c('none', 'arrow') \cr
#'      \code{linkSymbolSize} \tab numeric verctor, default c(10, 15) \cr
#'      \code{scaling} \tab numeric, default 1 \cr
#'      \code{gravity} \tab numeric, default 1 \cr
#'      \code{draggable} \tab TRUE|FALSE, default TRUE \cr
#'      \code{useWorker} \tab TRUE|FALSE, default FALSE \cr
#'      \code{steps} \tab numeric, default 1 \cr
#'      \code{nodes} \tab nodes list
#'      }}
#'  \item{map}{
#'    \tabular{ll}{
#'      \code{selectedMode} \tab NULL|'single'|'multiple', default NULL \cr
#'      \code{mapType} \tab 'china'|'world', default 'china' \cr
#'      \code{hoverable} \tab TRUE|FALSE, default TRUE \cr
#'      \code{dataRangeHoverLink} \tab TRUE|FALSE, default TRUE \cr
#'      \code{mapLocation} \tab list{x=.., y=.., width=.., height=..}, default
#'      list(x='center', y='center') \cr
#'      \code{mapValueCalculation} \tab 'sum'|'average', default 'sum' \cr
#'      \code{mapValuePrecision} \tab integer, default 0 \cr
#'      \code{showLegendSymbol} \tab TRUE|FALSE, default TRUE \cr
#'      \code{roam} \tab TRUE|FALSE|'scale'|'move', default FALSE \cr
#'      \code{scaleLimit} \tab numeric list(max=.., min=..), default NULL \cr
#'      \code{nameMap} \tab \code{\link{addNameMap}} \cr
#'      \code{textFixed} \tab list, e.g., list(China=c(10, -10)), default NULL \cr
#'      \code{geoCoord} \tab \code{\link{addGeoCoord}} \cr
#'      \code{heatmap} \tab \code{\link{addHeatMap}}
#'      }}
#'  \item{gauge}{
#'    \tabular{ll}{
#'      \code{legendHoverLink} \tab TRUE|FALSE, default TRUE. \cr
#'      \code{center} \tab percent or numeric vector, default c('50\%', '50\%') \cr
#'      \code{radius} \tab percent or numeric vector, default c(0, '75\%') \cr
#'      \code{startAngle} \tab numeric -360 ~ 360, default 225 \cr
#'      \code{endAngle} \tab numeric -360 ~ 360, default -45 \cr
#'      \code{min} \tab numeric, default 0 \cr
#'      \code{max} \tab numeric, default 100 \cr
#'      \code{splitNumber} \tab numeric, default 10 \cr
#'      \code{axisLine} \tab list, default \code{list(
#'       show=TRUE, lineStyle=list(
#'       color=list(list(0.2, '#228b22'), list(0.8, '#48b'), list(1, '#ff4500')),
#'       width=30))} \cr
#'      \code{axisTick} \tab list, default \code{list(
#'       show=TRUE, splitNumber=5, length=8, lineStyle=list(
#'       color='#eee', width=1, type='solid'))} \cr
#'      \code{axisLabel} \tab list, default \code{list(show=TRUE, formatter=NULL,
#'       textStyle=list(color='auto'))} \cr
#'      \code{splitLine} \tab list, default \code{list(show=TRUE,
#'       length=30, lineStyle=list(
#'       color='#eee', width=2, type='solid'))} \cr
#'      \code{pointer} \tab list, default \code{list(length='80\%', width=8,
#'       color='auto')} \cr
#'      \code{title} \tab list, default \code{list(show=TRUE, offsetCenter=list(0, '40\%'),
#'       textStyle=list(color='#333', fontSize=15))} \cr
#'      \code{detail} \tab list, default \code{list(show=TRUE, backgroundColor='rgba(0,0,0,0)',
#'       borderWidth=0, borderColor='#ccc', width=100, height=40, offsetCenter=list(0, '40\%'),
#'       formatter=NULL, textStyle=list(color='auto', fontSize=30))}
#'      }}
#'  \item{funnel}{
#'    \tabular{ll}{
#'      \code{legendHoverLink} \tab TRUE|FALSE, default TRUE. \cr
#'      \code{sort} \tab 'ascending'|'descending', default 'descending' \cr
#'      \code{min} \tab numeric, default 0 \cr
#'      \code{max} \tab numeric, default 100 \cr
#'      \code{x} \tab numeric, default 80 \cr
#'      \code{y} \tab numeric, default 60 \cr
#'      \code{x2} \tab numeric, default 80 \cr
#'      \code{y2} \tab numeric, default 60 \cr
#'      \code{width} \tab percent or numeric, default NULL \cr
#'      \code{height} \tab percent or numeric, default NULL \cr
#'      \code{funnelAlign} \tab 'left'|'right'|'center', default 'center' \cr
#'      \code{minSize} \tab percent, default '0\%' \cr
#'      \code{maxSize} \tab percent, default '100\%' \cr
#'      \code{gap} \tab numeric, default 0
#'      }}
#'  \item{eventRiver}{
#'    \tabular{ll}{
#'      \code{xAxisIndex} \tab 0|1, default 0 (main axis) \cr
#'      \code{legendHoverLink} \tab TRUE|FALSE, default TRUE. \cr
#'      \code{weight} \tab numeric, default 1
#'      }}
#'  \item{treemap}{
#'    \tabular{ll}{
#'      \code{center} \tab percent or numeric vector, default c('50\%', '50\%') \cr
#'      \code{size} \tab percent or numeric vector, default c('80\%', '80\%') \cr
#'      \code{root} \tab name of the root displayed currently
#'      }}
#'  \item{tree}{
#'    \tabular{ll}{
#'      \code{symbol} \tab \code{\link{setSymbols}} \cr
#'      \code{symbolSize} \tab numeric or \code{\link{JS}} function. \cr
#'      \code{roam} \tab TRUE|FALSE|'scale'|'move', default FALSE \cr
#'      \code{rootLocation} \tab list, could be list(x='center' | 'left' | 'right' | 'x\%' | {number},
#'      y='center' | 'top' | 'bottom' | 'y\%' | {number}) \cr
#'      \code{layerPadding} \tab numeric, default 100 \cr
#'      \code{nodePadding} \tab numeric, default 30 \cr
#'      \code{orient} \tab 'vertical' | 'horizontal' | 'radial', default 'vertical' \cr
#'      \code{direction} \tab "" or "inverse", default ""
#'      }}
#'  \item{wordCloud}{
#'    \tabular{ll}{
#'      \code{center} \tab percent or numeric vector, default c('50\%', '50\%') \cr
#'      \code{size} \tab percent or numeric vector, default c('80\%', '80\%') \cr
#'      \code{textRotation} \tab vector, -90 ~ 90, default c(0, 90) \cr
#'      \code{autoSize} \tab list, default \code{list(enable=TRUE, minSize=12)}
#'      }}
#'  \item{heatmap}{
#'    \tabular{ll}{
#'      \code{blurSize} \tab numeric, default 30 \cr
#'      \code{gradientColors} \tab \code{list(offset=.., color=..)} or color vector.
#'      default \code{list('blue', 'cyan', 'limegreen', 'yellow', 'red')} \cr
#'      \code{minAlpha} \tab numeric, default 0.05 \cr
#'      \code{valueScale} \tab numeric, default 1 \cr
#'      \code{opacity} \tab numeric, default 1
#'      }}
#' }
#' @references
#'   \url{http://echarts.baidu.com/echarts2/doc/option.html#title~series-i}
#' @return A modified echarts object
#' @export
#'
#' @examples
#' \dontrun{
#' g <- echartr(iris, Sepal.Width, Petal.Width, Species)
#' g %>% setSeries(series=3, symbol='emptyCircle')
#' }
setSeries <- function(chart, series=NULL, timeslots=NULL, ...){
    if (!inherits(chart, 'echarts'))
        stop('chart is not an Echarts object. ',
             'Check if you have missed a %>% in your pipe chain.')
    validTypes <- c(
        'line', 'bar', 'scatter', 'k', 'pie', 'radar', 'chord', 'force', 'map',
        'gauge', 'funnel', 'eventRiver', 'treemap', 'tree', 'wordCloud', 'heatmap')
    hasT <- 'timeline' %in% names(chart$x)
    lst <- list(...)

    # define t
    if (hasT){
        if (is.null(timeslots))
            timeslots <- seq_along(chart$x$options)
        else
            if (is.numeric(timeslots)){
                timeslots <- intersect(timeslots, seq_along(chart$x$options))
            }else{
                timeslots <- which(chart$x$timeline$data %in% timeslots)
            }
    }else{
        timeslots <- NULL
    }

    # define series
    lstAnalyzeSeries <- analyzeSeries(chart, series)
    series <- lstAnalyzeSeries$numSeries
    lvlseries <- lstAnalyzeSeries$strSeries
    allSeries <- lstAnalyzeSeries$allnumSeries
    lvlSeries <- lstAnalyzeSeries$allStrSeries
    newSeries <- lstAnalyzeSeries$strNewSeries

    # mergeList
    if (is.null(timeslots)){  # no timeline
        if (length(series) >0)
            for (s in unlist(series)){  # series is null, mono series
                if (! 'type' %in% names(lst))
                    type <- chart$x$series[[s]]$type
                else
                    type <- match.arg(lst$type, validTypes)
                chart$x$series[[s]] <- mergeList(
                    chart$x$series[[s]], filterSeriesParts(lst, type)
                )
            }

        if (length(newSeries) > 0){
            for (ns in seq_along(newSeries)){
                if ('type' %in% names(lst)){
                    type <- match.arg(lst$type, validTypes)
                    chart$x$series <- append(chart$x$series, list(
                        filterSeriesParts(lst, type)
                    ))
                }
            }
        }
    }else{  # with timeline
        for (t in timeslots){
            if (length(series) >0)
                for (s in series[[t]]){  # series is null, mono series
                    if (! 'type' %in% names(lst))
                        type <- chart$x$options[[t]]$series[[s]]$type
                    else
                        type <- match.arg(lst$type, validTypes)
                    chart$x$options[[t]]$series[[s]] <- mergeList(
                        chart$x$options[[t]]$series[[s]],
                        filterSeriesParts(lst, type)
                    )
                }

            if (length(newSeries) > 0){
                for (ns in seq_along(newSeries)){
                    if ('type' %in% names(lst)){
                        type <- match.arg(lst$type, validTypes)
                        chart$x$options[[t]]$series <- append(
                            chart$x$options[[t]]$series, list(
                                filterSeriesParts(lst, type)
                            )
                        )
                    }
                }
            }
        }
    }
    return(chart)
}

#' @export
#' @rdname setSeries
set_series <- setSeries

#' Define Aesthetic Elements of Echarts Object
#'
#' An Echarts object uses \code{itemStyle} heavily. You can use \code{itemStyle}
#' to compose an itemStyle list. \cr \cr
#' Contained in an itemStyle object are \cr
#' \describe{
#'   \item{atomic features}{'color', 'borderColor', 'borderWidth', 'barBorderColor',
#'   'barBorderRadius', 'barBorderWidth', which you can directly assign values}
#'   \item{object features}{'lineStyle', 'textStyle','areaStyle', 'chordStyle',
#'   'nodeStyle', 'linkStyle', which you can yield by \code{aesStyle} function family}
#'   \item{mixed object features}{'label' and 'labelLine', which contains other
#'   object features, such as 'lineStyle', 'textStyle'}
#' } \cr
#'  You can use \code{aesStyle} function family (\code{
#'  lineStyle, textStyle, areaStyle, aesChordSytle, labelStyle, labelLineStyle})
#'  to compose basic feature objects, and then group them into label or labelLine
#'  using \code{labelStyle / labelLineStyle}, and finally pack them into an itemStyle
#'  object using \code{itemStyle}.
#'
#' @param element String, could be 'text', 'line', 'area', 'chord', 'node', or 'link',
#' corresponding to \code{textStyle, lineStyle, areaStyle, chordStyle, nodeStyle,
#' linkStyle}
#' @param ... The params to form an aesthetic element \cr
#' The element-specific params list:
#' \describe{
#'  \item{\strong{itemStyle} (normal|emphasis)}{
#'    \tabular{ll}{
#'      \code{color} \tab color vector, 'rgba', hex color, or color names. \cr
#'      \code{lineStyle} \tab for line, k charts and markLine, \code{\link{lineStyle}} \cr
#'      \code{textStyle} \tab \code{\link{textStyle}} \cr
#'      \code{areaStyle} \tab for stacked line chart and map, \code{\link{areaStyle}} \cr
#'      \code{chordStyle} \tab for chord chart, \code{\link{chordStyle}} \cr
#'      \code{nodeStyle} \tab for force chart, \code{\link{nodeStyle}} \cr
#'      \code{linkStyle} \tab for force chart, \code{\link{linkStyle}} \cr
#'      \code{borderColor} \tab color vector, 'rgba', hex color, or color names. \cr
#'      \code{borderWidth} \tab for symbol, symbole, pie chart, map and markPoint, numeric \cr
#'      \code{barBorderColor} \tab for symbol, symbole, pie chart, map and markPoint, numeric \cr
#'      \code{barBorderRadius} \tab numeric vector length 1 or 4 (right-bottom-left-top), default 0 \cr
#'      \code{barBorderWidth} \tab numeric vector length 1 or 4 (right-bottom-left-top), default 00 \cr
#'      \code{label} \tab for line, bar, k, scatter, pie, map, force, funnel charts
#'       and markPoint, markLine, \code{\link{labelStyle}} \cr
#'      \code{labelLine} \tab for pie and funnel chart, \code{\link{labelLineStyle}}
#'      }}
#'  \item{\emph{label}}{
#'    \tabular{ll}{
#'      \code{show} \tab TRUE|FALSE, default TRUE \cr
#'      \code{position} \tab \itemize{
#'        \item for pie, 'outer'|'inner'; \cr
#'        \item for funnel, 'inner'|'left'|'right'; \cr
#'        \item for line, bar, k, scatter, 'top'|'right'|'inside'|'left'|'bottom'; \cr
#'        \item for bar, additionally 'insideLeft' | 'insideRight' | 'insideTop' | 'insideBottom'} \cr
#'      \code{rotate} \tab chord chart only. TRUE|FALSE, default FALSE \cr
#'      \code{distance} \tab chord and pie chart only. numeric, default 10 \cr
#'      \code{formatter} \tab \code{\link{setTooltip}} \cr
#'      \code{textStyle} \tab \code{\link{textStyle}} \cr
#'      \code{x} \tab treemap only, numeric \cr
#'      \code{y} \tab treemap only, numeric
#'      }}
#'  \item{\emph{labelLine}}{
#'    \tabular{ll}{
#'      \code{show} \tab TRUE|FALSE, default TRUE \cr
#'      \code{length} \tab numeric or 'auto', default 40 \cr
#'      \code{lineStyle} \tab \code{\link{lineStyle}}
#'      }}
#'  \item{textStyle}{
#'    \tabular{ll}{
#'      \code{color} \tab color vector, 'rgba', hex color, or color names.  \cr
#'      \code{decoration} \tab only for tooltip. string, default 'none' \cr
#'      \code{align} \tab 'left' | 'right' | 'center' \cr
#'      \code{baseline} \tab 'top' | 'bottom' | 'middle' \cr
#'      \code{fontFamily} \tab valid font family name \cr
#'      \code{fontSize} \tab numeric, default 12 \cr
#'      \code{fontStyle} \tab 'normal' | 'italic' | 'oblique', default 'normal' \cr
#'      \code{fontWeight} \tab 'normal' | 'bold' | 'bolder' | 'lighter' or numeric,
#'      default 'normal'
#'      }}
#'  \item{lineStyle}{
#'    \tabular{ll}{
#'      \code{color} \tab color vector, 'rgba', hex color, or color names. \cr
#'      \code{type} \tab 'solid' | 'dotted' | 'dashed', for tree, additionally
#'      'curve' | 'broken'. Default 'solid' \cr
#'      \code{width} \tab numeric \cr
#'      \code{shadowColor} \tab color vector, 'rgba', hex color, or color names. \cr
#'      \code{shadowBlur} \tab numeric, default 5 \cr
#'      \code{shadowOffsetX} \tab numeric, default 3 \cr
#'      \code{shadowOffsetY} \tab numeric, default 3
#'      }}
#'  \item{areaStyle}{
#'    \tabular{ll}{
#'      \code{color} \tab color vector, 'rgba', hex color, or color names. \cr
#'      \code{type} \tab only 'default'
#'      }}
#'  \item{chordStyle}{
#'    \tabular{ll}{
#'      \code{width} \tab numeric, default 1 \cr
#'      \code{color} \tab color vector, 'rgba', hex color, or color names. \cr
#'      \code{borderWidth} \tab numeric, default 1 \cr
#'      \code{borderColor} \tab color vector, 'rgba', hex color, or color names.
#'      }}
#'  \item{nodeStyle}{
#'    \tabular{ll}{
#'      \code{color} \tab color vector, 'rgba', hex color, or color names. \cr
#'      \code{borderWidth} \tab numeric, default 1 \cr
#'      \code{borderColor} \tab color vector, 'rgba', hex color, or color names.
#'      }}
#'  \item{linkStyle}{
#'    \tabular{ll}{
#'      \code{type} \tab 'curve'|'line' \cr
#'      \code{color} \tab color vector, 'rgba', hex color, or color names. default '#5182ab' \cr
#'      \code{width} \tab numeric, default 1
#'      }}
#' }
#'
#' @return A list
#' @export
#'
#' @examples
#' \dontrun{
#' lab <- labelStyle(show=TRUE, position='inside',
#'                 textStyle=textStyle(color='red'))
#' styLine <- lineStyle(color='#fff', width=4, shadowBlur=5)
#' itemStyle <- list(normal=itemStyle(lineStyle=styLine, label=lab),
#'                   emphasis=itemStyle(lineStyle=styLine, label=lab)
#'               )
#' }
aesStyle <- function(element=c('text', 'line', 'area', 'chord', 'node', 'link'),
...){
    lst <- list(...)
    element <- match.arg(element)
    validParts <- switch(
        element,
        text=c('color', 'decoration', 'align', 'baseline', 'fontFamily',
               'fontSize', 'fontStyle', 'fontWeight'),
        line=c('color', 'type', 'width', 'shadowColor', 'shadowBlur',
               'shadowOffsetX', 'shadowOffsetY'),
        area=c('color', 'type'),
        chord=c('width', 'color', 'borderWidth', 'borderColor'),
        node=c('color', 'borderWidth', 'borderColor'),
        link=c('type', 'color', 'width'))

    lst <- lst[intersect(names(lst), validParts)]
    if ('align' %in% names(lst))
        lst$align <- match.arg(lst$align, c('left', 'center', 'right'))
    if ('baseline' %in% names(lst)) lst$baseline <- match.arg(lst$baseline, c(
        'top', 'middle', 'bottom'))
    if ('type' %in% names(lst)) lst$type <- match.arg(lst$type, c(
        'solid', 'dotted', 'dashed', 'broken', 'curve', 'line', 'default'))
    if ('fontStyle' %in% names(lst)) lst$fontStyle <- match.arg(lst$fontStyle, c(
        'normal', 'italic', 'oblique'))
    if ('fontWeight' %in% names(lst)) stopifnot(
        is.numeric(lst$fontWeight) || lst$fontWeight %in% c(
            'normal', 'bold', 'bolder', 'lighter'))
    if ('width' %in% names(lst)) stopifnot(is.numeric(lst$width))
    if ('shadowBlur' %in% names(lst)) stopifnot(is.numeric(lst$shadowBlur))
    if ('shadowOffsetX' %in% names(lst)) stopifnot(is.numeric(lst$shadowOffsetX))
    if ('shadowOffsetY' %in% names(lst)) stopifnot(is.numeric(lst$shadowOffsetY))
    if ('borderWidth' %in% names(lst)) stopifnot(is.numeric(lst$borderWidth))
    if ('fontSize' %in% names(lst)) stopifnot(is.numeric(lst$fontSize))

    return(lst)
}

#' @export
#' @rdname aesStyle
aes_style <- aesStyle


#' @export
#' @rdname aesStyle
lineStyle <- function(...) aesStyle('line', ...)

#' @export
#' @rdname aesStyle
style_line <- lineStyle

#' @export
#' @rdname aesStyle
textStyle <- function(...) aesStyle('text', ...)

#' @export
#' @rdname aesStyle
style_text <- textStyle

#' @export
#' @rdname aesStyle
areaStyle <- function(...) aesStyle('area', ...)

#' @export
#' @rdname aesStyle
style_area <- areaStyle

#' @export
#' @rdname aesStyle
chordStyle <- function(...) aesStyle('chord', ...)

#' @export
#' @rdname aesStyle
style_chord <- chordStyle

#' @export
#' @rdname aesStyle
nodeStyle <- function(...) aesStyle('node', ...)

#' @export
#' @rdname aesStyle
style_node <- nodeStyle

#' @export
#' @rdname aesStyle
linkStyle <- function(...) aesStyle('link', ...)

#' @export
#' @rdname aesStyle
style_link <- linkStyle

#' @export
#' @rdname aesStyle
labelStyle <- function(...){
    lst <- list(...)
    validParts <- c(
        'show', 'position', 'rotate', 'distance', 'formatter', 'textStyle', 'x', 'y')
    if ('show' %in% names(lst)) stopifnot(is.logical(lst$show))
    if ('position' %in% names(lst)) lst$position <- match.arg(lst$position, c(
        'outer', 'inner', 'left', 'right', 'top', 'inside', 'bottom', 'insideLeft',
        'insideRight', 'insideTop', 'insdieBottom'
    ))
    if ('rotate' %in% names(lst)) stopifnot(is.logical(lst$rotate))
    if ('textStyle' %in% names(lst)) stopifnot(is.list(lst$textStyle))
    return(lst)
}

#' @export
#' @rdname aesStyle
style_label <- labelStyle

#' @export
#' @rdname aesStyle
labelLineStyle <- function(...){
    lst <- list(...)
    validParts <- c(
        'show', 'length', 'lineStyle')
    if ('show' %in% names(lst)) stopifnot(is.logical(lst$show))
    if ('length' %in% names(lst)) stoifnot(
        is.numeric(lst$length) || lst$length=='auto')
    if ('lineStyle' %in% names(lst)) stopifnot(is.list(lst$lineStyle))
    return(lst)
}

#' @export
#' @rdname aesStyle
style_labelline <- labelLineStyle

#' @export
#' @rdname aesStyle
style_ll <- labelLineStyle


#' @export
#' @rdname aesStyle
itemStyle <- function(...){
    lst <- list(...)
    validParts <- c(
        'color', 'lineStyle', 'textStyle', 'areaStyle', 'chordStyle', 'nodeStyle',
        'linkStyle', 'borderColor', 'borderWidth', 'barBorderColor', 'barBorderRadius',
        'barBorderWidth', 'label', 'labelLine')
    lst <- lst[intersect(names(lst), validParts)]
    return(lst)
}

#' @export
#' @rdname aesStyle
style_item <- itemStyle

#---------------------Legacy eAxis functions----------------------------
eAxis = function(
    # Create an axis for a chart
    #
    # Add an axis to a chart.
    #
    # This function modified a few default options for the axis component in
    # ECharts: 1) \code{scale = TRUE} (was \code{FALSE} by default in ECharts); 2)
    # \code{axisLine$onZero = FALSE} (was \code{TRUE} in ECharts).
    # rdname axis
    chart, which = c('x', 'y'),
    type = c('value', 'category', 'time', 'log'), show = TRUE,
    position = c('bottom', 'top', 'left', 'right'),
    name = '', nameLocation = c('end', 'start'), nameTextStyle = emptyList(),
    boundaryGap = c(0, 0), min = NULL, max = NULL, scale = TRUE, splitNumber = NULL,
    axisLine = list(show = TRUE, onZero = FALSE), axisTick = list(show = FALSE),
    axisLabel = list(show = TRUE), splitLine = list(show = TRUE),
    splitArea = list(show = FALSE), data = list()
) {
    which = match.arg(which)
    odata = getMeta(chart)[[which]]  # original data along the axis
    if (missing(type)) type = axisType(odata, which)
    if (missing(position)) position = if (which == 'x') 'bottom' else 'left'
    if (missing(data) && type == 'category') {
        data = I(levels(as.factor(odata)))
    }

    x = chart$x
    i = paste0(which, 'Axis')
    o = list(
        type = match.arg(type), show = show, position = match.arg(position),
        name = name, nameLocation = match.arg(nameLocation), nameTextStyle = nameTextStyle,
        boundaryGap = boundaryGap, min = min, max = max, scale = scale,
        splitNumber = splitNumber, axisLine = axisLine, axisTick = axisTick,
        axisLabel = axisLabel, splitLine = splitLine, splitArea = splitArea, data = data
    )
    if (length(x[[i]])) {
        # only merge the arguments that are not missing, e.g. eAxis(min = 0) will
        # only overide 'min' but will not overide the 'name' attribute
        a = intersect(names(as.list(match.call()[-1])), names(o))  #;browser()
        x[[i]] = mergeList(x[[i]], o[a])
    } else {
        x[[i]] = mergeList(x[[i]], o)
    }
    chart$x = x

    return(chart)
}


eXAxis = function(chart, ...) {
    # rdname axis
    eAxis(chart, which = 'x', ...)
}


eYAxis = function(chart, ...) {
    # rdname axis
    eAxis(chart, which = 'y', ...)
}
